From 2372764a7fdf9034ab4eb01c6fbdf1970c12c3ee Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 6 Jul 2026 22:01:53 +0200 Subject: [PATCH 1/5] Drop PHP 7 support --- .github/workflows/ci.yml | 2 +- README.md | 5 +---- composer.json | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 198a2a5..74998ba 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5', '8.6'] + php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5', '8.6'] os: [ubuntu-latest, windows-latest] steps: diff --git a/README.md b/README.md index 8942f07..f4b4ccb 100755 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ XP Reflection [![Build status on GitHub](https://github.com/xp-framework/reflection/workflows/Tests/badge.svg)](https://github.com/xp-framework/reflection/actions) [![XP Framework Module](https://raw.githubusercontent.com/xp-framework/web/master/static/xp-framework-badge.png)](https://github.com/xp-framework/core) [![BSD Licence](https://raw.githubusercontent.com/xp-framework/web/master/static/licence-bsd.png)](https://github.com/xp-framework/core/blob/master/LICENCE.md) -[![Requires PHP 7.4+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-7_4plus.svg)](http://php.net/) -[![Supports PHP 8.0+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-8_0plus.svg)](http://php.net/) +[![Requires PHP 8.0+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-8_0plus.svg)](http://php.net/) [![Latest Stable Version](https://poser.pugx.org/xp-framework/reflection/version.svg)](https://packagist.org/packages/xp-framework/reflection) This library provides a replacement for the XP Framework's reflection API. @@ -14,8 +13,6 @@ Features -------- **Concise**: This library aims at reducing code noise of the form `if (hasX(...)) { getX() }` by simply returning NULL from its accessor methods. Null handling has improved with the introduction of the null-coalesce operator `??` in PHP 7 and the null-safe invocation operator `?->` in PHP 8 (and in [XP Compiler](https://github.com/xp-framework/compiler)). -**PHP 7 & 8**: This library handles PHP 8 attributes in both PHP 7 (*using the compiler's AST library*) and PHP 8 (*using its native reflection API*). - **Subcommand**: This library provides an [RFC #0303 integration](https://github.com/xp-framework/rfc/issues/303) and offers a "reflect" subcommand for the new XP runners. See `xp help reflect` on how to use it. API diff --git a/composer.json b/composer.json index 401054c..dec892d 100755 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require" : { "xp-framework/core": "^12.0 | ^11.0 | ^10.13", "xp-framework/ast": "^13.0 | ^12.0 | ^11.0 | ^10.0 | ^9.0 | ^8.0 | ^7.6", - "php" : ">=7.4.0" + "php" : ">=8.0.0" }, "require-dev" : { "xp-framework/test": "^2.0 | ^1.0" From 17f14db4a79e613aa7efb19ead2afe2fb3e0db7d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 6 Jul 2026 22:05:16 +0200 Subject: [PATCH 2/5] Remove Reflection::annotations() --- src/main/php/lang/Reflection.class.php | 8 ++------ .../lang/reflection/unittest/ReflectionTest.class.php | 10 ---------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/main/php/lang/Reflection.class.php b/src/main/php/lang/Reflection.class.php index e098269..a388bc6 100755 --- a/src/main/php/lang/Reflection.class.php +++ b/src/main/php/lang/Reflection.class.php @@ -1,6 +1,6 @@ = 80000 ? new FromAttributes() : new FromSyntaxTree(); - } - /** Lazy-loads meta information extraction */ public static function meta(): MetaInformation { - return self::$meta ?? self::$meta= new MetaInformation(self::annotations(PHP_VERSION_ID)); + return self::$meta ?? self::$meta= new MetaInformation(new FromAttributes()); } /** diff --git a/src/test/php/lang/reflection/unittest/ReflectionTest.class.php b/src/test/php/lang/reflection/unittest/ReflectionTest.class.php index 6b58312..c5a380a 100755 --- a/src/test/php/lang/reflection/unittest/ReflectionTest.class.php +++ b/src/test/php/lang/reflection/unittest/ReflectionTest.class.php @@ -52,16 +52,6 @@ public function of_package_name() { Assert::instance(Package::class, Reflection::of('lang.reflection.unittest')); } - #[Test, Values([70000, 70100, 70200, 70300, 70400])] - public function parser_for_php7($versionId) { - Assert::instance(FromSyntaxTree::class, Reflection::annotations($versionId)); - } - - #[Test, Values([80000, 80100, 80200])] - public function parser_for_php8($versionId) { - Assert::instance(FromAttributes::class, Reflection::annotations($versionId)); - } - #[Test, Expect(ClassNotFoundException::class)] public function of_non_existant() { Reflection::of('non.existant.Type'); From 5566009d4408d4f54806fecd15c1a8c865349aa9 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 6 Jul 2026 22:07:16 +0200 Subject: [PATCH 3/5] Drop parsing from AST --- composer.json | 1 - .../php/lang/meta/FromSyntaxTree.class.php | 237 ------------------ src/main/php/lang/meta/SyntaxTree.class.php | 196 --------------- 3 files changed, 434 deletions(-) delete mode 100755 src/main/php/lang/meta/FromSyntaxTree.class.php delete mode 100755 src/main/php/lang/meta/SyntaxTree.class.php diff --git a/composer.json b/composer.json index dec892d..aed0c48 100755 --- a/composer.json +++ b/composer.json @@ -7,7 +7,6 @@ "keywords": ["module", "xp"], "require" : { "xp-framework/core": "^12.0 | ^11.0 | ^10.13", - "xp-framework/ast": "^13.0 | ^12.0 | ^11.0 | ^10.0 | ^9.0 | ^8.0 | ^7.6", "php" : ">=8.0.0" }, "require-dev" : { diff --git a/src/main/php/lang/meta/FromSyntaxTree.class.php b/src/main/php/lang/meta/FromSyntaxTree.class.php deleted file mode 100755 index 6a1b0a5..0000000 --- a/src/main/php/lang/meta/FromSyntaxTree.class.php +++ /dev/null @@ -1,237 +0,0 @@ -getTraitNames() as $declared) { - if ($type= $tree->type($declared)) return $type; - } - throw new IllegalStateException('No part of '.$class->name.' declared in tree'); - } - - /** Locates an anonymous class creation expression */ - private function anonymous($tree, $start, $end) { - foreach ($tree->children() as $child) { - yield from $this->anonymous($child, $start, $end); - if ('newclass' === $child->kind && $child->line >= $start && $child->line <= $end) yield $child; - } - } - - /** Returns the syntax tree for a given type using a cache */ - private function tree($class, $file) { - if (null === ($tree= $this->cache[$file] ?? null)) { - $this->cache[$file]= $tree= self::$lang->parse(new Tokens(file_get_contents($file), $file))->tree(); - if (sizeof($this->cache) > self::CACHE_SIZE) unset($this->cache[key($this->cache)]); - } - - return new SyntaxTree($tree, $class->isAnonymous() - ? $this->anonymous($tree, $class->getStartLine(), $class->getEndLine())->current()->definition - : $tree->type($class->name) ?? $this->partial($tree, $class) - ); - } - - private function parse($code, $resolver) { - if (null === self::$parse) { - - // Parse lambdas and closures into code - self::$parse= clone self::$lang; - self::$parse->prefix('fn', 0, function($parse, $token) { - $signature= $this->signature($parse); - $parse->expecting('=>', 'fn'); - - // Parse expression - $code= ''; - $b= $c= 0; - do { - switch ($parse->token->value) { - case '(': $b++; break; - case ')': $b--; if ($b < 0) break 2; else break; - case '[': $c++; break; - case ']': $c--; if ($c < 0) break 2; else break; - case ',': if ($c <= 0 && $b <= 0) break 2; else break; // outside of arrays or argument lists - case '$': $parse->forward(); $parse->token->value= '$'.$parse->token->value; break; - } - $code.= ' '.$parse->token->value; - $parse->forward(); - } while (';' !== $parse->token->value); - - $params= ''; - foreach ($signature->parameters as $param) { - $params.= - ', '. - ($param->type ? $param->type->literal() : ''). - ($param->variadic ? '...' : ''). - ($param->reference ? ' &$': ' $'). - $param->name. - ($param->default ? '='.$param->default->expression : '') - ; - } - $return= $signature->returns ? ':'.$signature->returns->literal() : ''; - if (0 === strncmp($code, ' throw ', 7)) { - return new Code('function('.substr($params, 2).')'.$return.' {'.$code.'; }'); - } else { - return new Code('function('.substr($params, 2).')'.$return.' { return'.$code.'; }'); - } - }); - - $function= function($parse, $token) { - $signature= $this->signature($parse); - - // Parse body - $code= ''; - $b= 0; - do { - if ('{' === $parse->token->value) { - $b++; - } else if ('}' === $parse->token->value) { - if (0 === --$b) break; - } else if ('$' === $parse->token->value) { - $parse->forward(); - $parse->token->value= '$'.$parse->token->value; - } - $code.= ' '.$parse->token->value; - $parse->forward(); - } while (null !== $parse->token->value); - $parse->forward(); - - $params= ''; - foreach ($signature->parameters as $param) { - $params.= - ', '. - ($param->type ? $param->type->literal() : ''). - ($param->variadic ? '...' : ''). - ($param->reference ? ' &$': ' $'). - $param->name. - ($param->default ? '='.$param->default->expression : '') - ; - } - $return= $signature->returns ? ':'.$signature->returns->literal() : ''; - return new Code('function('.substr($params, 2).')'.$return.$code.' }'); - }; - - // Function expressions and function expressions used as statement - self::$parse->prefix('function', 0, $function); - self::$parse->stmt('function', function($parse, $token) use($function) { - if ('(' === $parse->token->value) return $function->call($this, $parse, $token); - - $name= $parse->token->value; - $parse->forward(); - $signature= $this->signature($parse); - $parse->expecting('{', 'function'); - $statements= $this->statements($parse); - $parse->expecting('}', 'function'); - - return new FunctionDeclaration($name, $signature, $statements, $token->line); - }); - } - return self::$parse->parse(new Tokens($code.';', '(evaluated)'), $resolver); - } - - public function evaluate($arg, $code) { - $tree= $arg instanceof SyntaxTree ? $arg : $this->tree($arg, $arg->getFileName()); - $parsed= self::parse($code, $tree->resolver())->tree()->children(); - if (1 === sizeof($parsed)) { - return $parsed[0]->visit($tree); - } - - throw new IllegalArgumentException('Given code must be a single expression'); - } - - /** - * Evaluates annotation values, including special-case handling for the - * named argument `eval`. - * - * @param lang.annotations.SyntaxTree $tree - * @param lang.ast.nodes.Annotated $annotated - * @return [:var] - */ - private function annotations($tree, $annotated) { - if (null === $annotated->annotations) return []; - - $r= []; - foreach ($annotated->annotations as $type => $args) { - $ptr= &$r[$type]; - - if (!isset($args['eval'])) { - $ptr= []; - foreach ($args as $name => $argument) { - $ptr[$name]= $argument->visit($tree); - } - } else if ($args['eval'] instanceof ArrayLiteral) { - $ptr= []; - $i= 0; - foreach ($args['eval']->values as list($key, $value)) { - $ptr[$key ? $key->visit($tree) : $i++]= $this->evaluate($tree, $value->visit($tree).';'); - } - } else { - $ptr= [$this->evaluate($tree, $args['eval']->visit($tree).';')]; - } - } - return $r; - } - - public function imports($reflect) { - $resolver= $this->tree($reflect, $reflect->getFileName())->resolver(); - $imports= []; - foreach ($resolver->imports as $alias => $type) { - $imports[$alias]= ltrim($type, '\\'); - } - return $imports; - } - - /** @return iterable */ - public function ofType($reflect) { - $tree= $this->tree($reflect, $reflect->getFileName()); - return $this->annotations($tree, $tree->type()); - } - - /** @return iterable */ - public function ofConstant($reflect) { - $class= $reflect->getDeclaringClass(); - $tree= $this->tree($class, $class->getFileName()); - return $this->annotations($tree, $tree->type()->constant($reflect->name)); - } - - /** @return iterable */ - public function ofProperty($reflect) { - $class= $reflect->getDeclaringClass(); - $tree= $this->tree($class, $class->getFileName()); - return $this->annotations($tree, $tree->type()->property($reflect->name)); - } - - /** @return iterable */ - public function ofMethod($reflect) { - $tree= $this->tree($reflect->getDeclaringClass(), $reflect->getFileName()); - return $this->annotations($tree, $tree->type()->method($reflect->name)); - } - - /** @return iterable */ - public function ofParameter($method, $reflect) { - $class= $reflect->getDeclaringClass(); - $tree= $this->tree($class, $class->getMethod($method->name)->getFileName()); - return $this->annotations($tree, $tree->type() - ->method($method->name) - ->signature - ->parameters[$reflect->getPosition()] - ); - } -} \ No newline at end of file diff --git a/src/main/php/lang/meta/SyntaxTree.class.php b/src/main/php/lang/meta/SyntaxTree.class.php deleted file mode 100755 index 4fd137d..0000000 --- a/src/main/php/lang/meta/SyntaxTree.class.php +++ /dev/null @@ -1,196 +0,0 @@ -tree= $tree; - $this->type= $type; - } - - /** @return lang.ast.TypeDeclaration */ - public function type() { return $this->type; } - - public function resolver() { return $this->tree->scope(); } - - private function resolve($type) { - $name= $type instanceof Type ? $type->literal() : $type; - - if ('self' === $name) { - $resolved= $this->type->name; - } else if ('parent' === $name) { - $resolved= $this->tree->scope()->parent; - } else { - return $name; - } - - return $resolved instanceof Type ? $resolved->literal() : $resolved; - } - - /** - * Evaluates code - * - * @param lang.ast.Node $self - * @return var - */ - public function code($self) { - return eval('return '.$self->value.';'); - } - - /** - * Evaluates literals - * - * @param lang.ast.Node $self - * @return var - */ - public function literal($self) { - return eval('return '.$self->expression.';'); - } - - /** - * Evaluates arrays - * - * @param lang.ast.Node $self - * @return var - */ - public function array($self) { - $r= []; - foreach ($self->values as list($key, $value)) { - if (null === $key) { - $r[]= $value->visit($this); - } else { - $r[$key->visit($this)]= $value->visit($this); - } - } - return $r; - } - - /** - * Evaluates new operator - * - * @param lang.ast.Node $self - * @return var - */ - public function new($self) { - $c= new ReflectionClass($this->resolve($self->type)); - $arguments= []; - foreach ($self->arguments as $key => $node) { - $arguments[$key]= $node->visit($this); - } - return $c->newInstance(...$arguments); - } - - /** - * Evaluates scope resolution operators - * - * @param lang.ast.Node $self - * @return var - */ - public function scope($self) { - $c= $this->resolve($self->type); - - // Use PHP reflection API to access members' runtime values - if ($self->member instanceof Variable) { - return (new ReflectionClass($c))->getStaticPropertyValue($self->member->pointer ?? $self->member->name); - } else if ($self->member instanceof Literal) { - return 'class' === $self->member->expression - ? substr($c, 1) - : (new ReflectionClass($c))->getConstant($self->member->expression) - ; - } else if ($self->member instanceof InvokeExpression) { - $arguments= []; - foreach ($self->member->arguments as $key => $node) { - $arguments[$key]= $node->visit($this); - } - return (new ReflectionClass($c))->getMethod($self->member->expression)->invokeArgs(null, $arguments); - } else { - throw new IllegalAccessException('Cannot resolve '.$c.'::'.$self->member->kind); - } - } - - /** - * Evaluates class constants - * - * @param lang.ast.Node $self - * @return var - */ - public function const($self) { - return $self->expression->visit($this); - } - - /** - * Evaluates properties - * - * @param lang.ast.Node $self - * @return var - */ - public function property($self) { - return $self->expression->visit($this); - } - - /** - * Evaluates unary prefix operators - * - * @param lang.ast.Node $self - * @return var - */ - public function prefix($self) { - switch ($self->operator) { - case '+': return +$self->expression->visit($this); - case '-': return -$self->expression->visit($this); - case '~': return ~$self->expression->visit($this); - case '!': return !$self->expression->visit($this); - } - } - - /** - * Evaluates binary operators - * - * @param lang.ast.Node $self - * @return var - */ - public function binary($self) { - switch ($self->operator) { - case '.': return $self->left->visit($this).$self->right->visit($this); - case '+': return $self->left->visit($this) + $self->right->visit($this); - case '-': return $self->left->visit($this) - $self->right->visit($this); - case '*': return $self->left->visit($this) * $self->right->visit($this); - case '/': return $self->left->visit($this) / $self->right->visit($this); - case '%': return $self->left->visit($this) % $self->right->visit($this); - case '^': return $self->left->visit($this) ^ $self->right->visit($this); - case '|': return $self->left->visit($this) | $self->right->visit($this); - case '&': return $self->left->visit($this) & $self->right->visit($this); - case '**': return $self->left->visit($this) ** $self->right->visit($this); - case '?:': return $self->left->visit($this) ?: $self->right->visit($this); - case '??': return $self->left->visit($this) ?? $self->right->visit($this); - case '<<': return $self->left->visit($this) << $self->right->visit($this); - case '>>': return $self->left->visit($this) >> $self->right->visit($this); - case '||': return $self->left->visit($this) || $self->right->visit($this); - case '&&': return $self->left->visit($this) && $self->right->visit($this); - case '==': return $self->left->visit($this) == $self->right->visit($this); - case '!=': return $self->left->visit($this) != $self->right->visit($this); - case '<': return $self->left->visit($this) < $self->right->visit($this); - case '>': return $self->left->visit($this) > $self->right->visit($this); - case '<=': return $self->left->visit($this) <= $self->right->visit($this); - case '>=': return $self->left->visit($this) >= $self->right->visit($this); - case '<=>': return $self->left->visit($this) <=> $self->right->visit($this); - case '===': return $self->left->visit($this) === $self->right->visit($this); - case '!==': return $self->left->visit($this) !== $self->right->visit($this); - } - } - - /** - * Evaluates ternary operators - * - * @param lang.ast.Node $self - * @return var - */ - public function ternary($self) { - return $self->condition->visit($this) ? $self->expression->visit($this) : $self->otherwise->visit($this); - } -} \ No newline at end of file From 19658289ea3223c0e78f06a685de6af81b468b02 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 6 Jul 2026 22:14:04 +0200 Subject: [PATCH 4/5] Remove PHP 7 named arguments support --- .../php/lang/reflection/Annotation.class.php | 6 +- .../php/lang/reflection/Constructor.class.php | 5 +- .../php/lang/reflection/Initializer.class.php | 3 +- src/main/php/lang/reflection/Method.class.php | 3 +- .../php/lang/reflection/Routine.class.php | 28 ----- src/main/php/lang/reflection/Type.class.php | 3 +- .../reflection/unittest/AcceptsTest.class.php | 58 +++++----- .../unittest/AnnotationTest.class.php | 4 +- .../unittest/ArgumentPassingTest.class.php | 105 ------------------ 9 files changed, 35 insertions(+), 180 deletions(-) delete mode 100644 src/test/php/lang/reflection/unittest/ArgumentPassingTest.class.php diff --git a/src/main/php/lang/reflection/Annotation.class.php b/src/main/php/lang/reflection/Annotation.class.php index f63c339..042c58d 100755 --- a/src/main/php/lang/reflection/Annotation.class.php +++ b/src/main/php/lang/reflection/Annotation.class.php @@ -47,11 +47,7 @@ public function argument($key) { return $this->arguments[$key] ?? null; } */ public function newInstance() { try { - $pass= PHP_VERSION_ID < 80000 && $this->arguments - ? Routine::pass(new ReflectionMethod($this->type, '__construct'), $this->arguments) - : $this->arguments - ; - return new $this->type(...$pass); + return new $this->type(...$this->arguments); } catch (ArgumentCountError $e) { throw new CannotInstantiate($this->type, $e); } catch (TypeError $e) { diff --git a/src/main/php/lang/reflection/Constructor.class.php b/src/main/php/lang/reflection/Constructor.class.php index b1665fb..5010afd 100755 --- a/src/main/php/lang/reflection/Constructor.class.php +++ b/src/main/php/lang/reflection/Constructor.class.php @@ -36,7 +36,6 @@ public function toString() { */ public function newInstance(array $args= []) { try { - $pass= PHP_VERSION_ID < 80000 && $args ? self::pass($this->reflect, $args) : $args; // Workaround for non-public constructors: Set accessible, then manually // invoke after creating an instance without invoking the constructor. @@ -47,10 +46,10 @@ public function newInstance(array $args= []) { // see https://wiki.php.net/rfc/make-reflection-setaccessible-no-op PHP_VERSION_ID < 80100 && $this->reflect->setAccessible(true); - $this->reflect->invokeArgs($instance, $pass); + $this->reflect->invokeArgs($instance, $args); return $instance; } else { - return $this->class->newInstanceArgs($pass); + return $this->class->newInstanceArgs($args); } } catch (ReflectionException|ArgumentCountError|TypeError $e) { throw new CannotInstantiate($this->class, $e); diff --git a/src/main/php/lang/reflection/Initializer.class.php b/src/main/php/lang/reflection/Initializer.class.php index 962c5a6..9baed47 100755 --- a/src/main/php/lang/reflection/Initializer.class.php +++ b/src/main/php/lang/reflection/Initializer.class.php @@ -55,8 +55,7 @@ public function newInstance(array $args= []) { if (null === $this->function) return $instance; try { - $pass= PHP_VERSION_ID < 80000 && $args ? Routine::pass($this->reflect, $args) : $args; - $this->function->__invoke($instance, $pass); + $this->function->__invoke($instance, $args); return $instance; } catch (ReflectionException|ArgumentCountError|TypeError $e) { throw new CannotInstantiate($this->class, $e); diff --git a/src/main/php/lang/reflection/Method.class.php b/src/main/php/lang/reflection/Method.class.php index cfe43da..af95dac 100755 --- a/src/main/php/lang/reflection/Method.class.php +++ b/src/main/php/lang/reflection/Method.class.php @@ -50,12 +50,11 @@ public function closure(?object $instance= null) { */ public function invoke(?object $instance, $args= []) { try { - $pass= PHP_VERSION_ID < 80000 && $args ? self::pass($this->reflect, $args) : $args; // TODO: Remove superfluous call to setAccessible() if on PHP8.1+ // see https://wiki.php.net/rfc/make-reflection-setaccessible-no-op PHP_VERSION_ID < 80100 && $this->reflect->setAccessible(true); - return $this->reflect->invokeArgs($instance, $pass); + return $this->reflect->invokeArgs($instance, $args); } catch (ReflectionException|ArgumentCountError|TypeError $e) { throw new CannotInvoke($this, $e); } catch (Throwable $e) { diff --git a/src/main/php/lang/reflection/Routine.class.php b/src/main/php/lang/reflection/Routine.class.php index 535567c..0f6a846 100755 --- a/src/main/php/lang/reflection/Routine.class.php +++ b/src/main/php/lang/reflection/Routine.class.php @@ -91,32 +91,4 @@ public function parameter($arg) { public function parameters(): Parameters { return new Parameters($this->reflect); } - - /** Support named arguments for PHP 7.X */ - public static function pass($reflect, $args) { - $pass= []; - foreach ($reflect->getParameters() as $i => $param) { - if ($param->isVariadic()) { - while ($args) $pass[]= array_shift($args); - break; - } else if (array_key_exists($param->name, $args)) { - $pass[]= $args[$param->name]; - unset($args[$param->name]); - } else if (array_key_exists($i, $args)) { - $pass[]= $args[$i]; - unset($args[$i]); - } else if ($param->isOptional()) { - $pass[]= $param->getDefaultValue(); - } else { - throw new ReflectionException('Missing parameter $'.$param->name); - } - } - - // Check for excess named parameters - if ($args && is_string($excess= key($args))) { - throw new Error('Unknown named parameter $'.$excess); - } - - return $pass; - } } \ No newline at end of file diff --git a/src/main/php/lang/reflection/Type.class.php b/src/main/php/lang/reflection/Type.class.php index e8b9427..4c479f3 100755 --- a/src/main/php/lang/reflection/Type.class.php +++ b/src/main/php/lang/reflection/Type.class.php @@ -204,8 +204,7 @@ public function newInstance(... $args) { $constructor= $this->reflect->hasMethod('__construct'); try { if ($constructor) { - $pass= PHP_VERSION_ID < 80000 && $args ? Routine::pass($this->reflect->getMethod('__construct'), $args) : $args; - return $this->reflect->newInstanceArgs($pass); + return $this->reflect->newInstanceArgs($args); } else { return $this->reflect->newInstance(); } diff --git a/src/test/php/lang/reflection/unittest/AcceptsTest.class.php b/src/test/php/lang/reflection/unittest/AcceptsTest.class.php index b9958e8..bbb2990 100755 --- a/src/test/php/lang/reflection/unittest/AcceptsTest.class.php +++ b/src/test/php/lang/reflection/unittest/AcceptsTest.class.php @@ -161,37 +161,33 @@ private function fixtures() { yield [$t, ['test', 1], null, false]; yield [$t, ['test', 'works'], null, true]; - if (PHP_VERSION_ID >= 70100) { - $t= $this->type('(?string $arg)'); - yield [$t, [], null, false]; - yield [$t, [null], null, true]; - yield [$t, ['test'], null, true]; - yield [$t, [$this], null, false]; - } - - if (PHP_VERSION_ID >= 80000) { - $t= $this->type('(string|int $arg)'); - yield [$t, [1], null, true]; - yield [$t, ['test'], null, true]; - yield [$t, [$this], null, false]; - - $t= $this->type('(string|int|null $arg)'); - yield [$t, [1], null, true]; - yield [$t, ['test'], null, true]; - yield [$t, [null], null, true]; - yield [$t, [$this], null, false]; - - $t= $this->type('(string|int... $arg)'); - yield [$t, ['test'], null, true]; - yield [$t, ['test', 1], null, true]; - yield [$t, ['test', $this], null, false]; - - $t= $this->type('(string|int|null... $arg)'); - yield [$t, ['test'], null, true]; - yield [$t, [null], null, true]; - yield [$t, ['test', 1], null, true]; - yield [$t, ['test', $this], null, false]; - } + $t= $this->type('(?string $arg)'); + yield [$t, [], null, false]; + yield [$t, [null], null, true]; + yield [$t, ['test'], null, true]; + yield [$t, [$this], null, false]; + + $t= $this->type('(string|int $arg)'); + yield [$t, [1], null, true]; + yield [$t, ['test'], null, true]; + yield [$t, [$this], null, false]; + + $t= $this->type('(string|int|null $arg)'); + yield [$t, [1], null, true]; + yield [$t, ['test'], null, true]; + yield [$t, [null], null, true]; + yield [$t, [$this], null, false]; + + $t= $this->type('(string|int... $arg)'); + yield [$t, ['test'], null, true]; + yield [$t, ['test', 1], null, true]; + yield [$t, ['test', $this], null, false]; + + $t= $this->type('(string|int|null... $arg)'); + yield [$t, ['test'], null, true]; + yield [$t, [null], null, true]; + yield [$t, ['test', 1], null, true]; + yield [$t, ['test', $this], null, false]; } #[Test, Values(from: 'fixtures')] diff --git a/src/test/php/lang/reflection/unittest/AnnotationTest.class.php b/src/test/php/lang/reflection/unittest/AnnotationTest.class.php index 7bb4cde..90a9e15 100755 --- a/src/test/php/lang/reflection/unittest/AnnotationTest.class.php +++ b/src/test/php/lang/reflection/unittest/AnnotationTest.class.php @@ -202,7 +202,7 @@ public function with_param_type($function) { $f= $t->annotation(Annotated::class)->argument(0); $type= (new ReflectionFunction($f))->getParameters()[0]->getType(); - Assert::equals('int', PHP_VERSION_ID >= 70100 ? $type->getName() : (string)$type); + Assert::equals('int', $type->getName()); } #[Test, Values(['function(): int { return 6100; }', 'fn(): int => 6100'])] @@ -211,7 +211,7 @@ public function with_return_type($function) { $f= $t->annotation(Annotated::class)->argument(0); $type= (new ReflectionFunction($f))->getReturnType(); - Assert::equals('int', PHP_VERSION_ID >= 70100 ? $type->getName() : (string)$type); + Assert::equals('int', $type->getName()); } #[Test] diff --git a/src/test/php/lang/reflection/unittest/ArgumentPassingTest.class.php b/src/test/php/lang/reflection/unittest/ArgumentPassingTest.class.php deleted file mode 100644 index 18e70da..0000000 --- a/src/test/php/lang/reflection/unittest/ArgumentPassingTest.class.php +++ /dev/null @@ -1,105 +0,0 @@ - null); - Assert::equals([1, 2], Routine::pass($f, [1, 2])); - } - - #[Test] - public function pass_ordered_null() { - $f= new ReflectionFunction(fn($a, $b) => null); - Assert::equals([null, 2], Routine::pass($f, [null, 2])); - } - - #[Test, Expect(class: Error::class, message: 'Missing parameter $a')] - public function missing() { - $f= new ReflectionFunction(fn($a, $b) => null); - Routine::pass($f, []); - } - - #[Test, Expect(class: Error::class, message: 'Missing parameter $b')] - public function missing_ordered() { - $f= new ReflectionFunction(fn($a, $b) => null); - Routine::pass($f, [1]); - } - - #[Test] - public function pass_named() { - $f= new ReflectionFunction(fn($a, $b) => null); - Assert::equals([1, 2], Routine::pass($f, ['a' => 1, 'b' => 2])); - } - - #[Test] - public function pass_named_null() { - $f= new ReflectionFunction(fn($a, $b) => null); - Assert::equals([null, 2], Routine::pass($f, ['a' => null, 'b' => 2])); - } - - #[Test] - public function pass_named_out_of_order() { - $f= new ReflectionFunction(fn($a, $b) => null); - Assert::equals([1, 2], Routine::pass($f, ['b' => 2, 'a' => 1])); - } - - #[Test] - public function omit_optional() { - $f= new ReflectionFunction(fn($a, $b= 0, $c= 0) => null); - Assert::equals([1, 0, 2], Routine::pass($f, ['a' => 1, 'c' => 2])); - } - - #[Test, Expect(class: Error::class, message: 'Missing parameter $b')] - public function missing_named() { - $f= new ReflectionFunction(fn($a, $b) => null); - Routine::pass($f, ['a' => 1]); - } - - #[Test, Expect(class: Error::class, message: 'Unknown named parameter $unknown')] - public function unknown_named() { - $f= new ReflectionFunction(fn($a, $b) => null); - Routine::pass($f, ['a' => 1, 'b' => 2, 'unknown' => null]); - } - - #[Test] - public function pass_named_and_ordered() { - $f= new ReflectionFunction(fn($a, $b) => null); - Assert::equals([1, 2], Routine::pass($f, [1, 'b' => 2])); - } - - #[Test] - public function pass_too_many() { - $f= new ReflectionFunction(fn($a, $b) => null); - Assert::equals([1, 2], Routine::pass($f, [1, 2, 3])); - } - - #[Test] - public function pass_optional() { - $f= new ReflectionFunction(fn($a, $b= 0) => null); - Assert::equals([1, 2], Routine::pass($f, [1, 2])); - } - - #[Test] - public function pass_without_optional() { - $f= new ReflectionFunction(fn($a, $b= 0) => null); - Assert::equals([1, 0], Routine::pass($f, [1])); - } - - #[Test] - public function pass_variadic() { - $f= new ReflectionFunction(fn(... $a) => null); - Assert::equals([1, 2], Routine::pass($f, [1, 2])); - } - - #[Test] - public function pass_variadic_after() { - $f= new ReflectionFunction(fn($a, ... $b) => null); - Assert::equals([1, 2, 3], Routine::pass($f, [1, 2, 3])); - } -} \ No newline at end of file From 1311f1b05a5277bac9ca780172a0ad4c5c1f8ac6 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 6 Jul 2026 22:40:06 +0200 Subject: [PATCH 5/5] Add changelog entry --- ChangeLog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 64306c8..3b08eeb 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,13 @@ XP Reflection ChangeLog ## ?.?.? / ????-??-?? +## 4.0.0 / ????-??-?? + +* Merged PR #70: Drop PHP 7 support. This implements the last phase of + xp-framework/rfc#343. The ninimum required PHP version goes from + 7.4.0 to 8.0.0! + (@thekid) + ## 3.7.0 / 2026-07-05 * Made compatible with `xp-framework/ast` version 13.0.0 - adding support