diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 5feb06ea..42a6481c 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -16,7 +16,7 @@ jobs: - "lowest" - "highest" php-version: - - "7.4" + - "8.0" operating-system: - "ubuntu-latest" diff --git a/composer.json b/composer.json index 945fe2bf..4cd8ef00 100644 --- a/composer.json +++ b/composer.json @@ -22,15 +22,16 @@ } }, "require": { - "php": ">=7.4", + "php": ">=8.0", "nikic/php-parser": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^9.0", + "tracy/tracy": "^2.10" }, "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.x-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 80281fee..88f4b939 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,19 +1,13 @@ - - - - - ./tests/ - - - - - - ./src/ - - + + + + ./src/ + + + + + ./tests/ + + diff --git a/src/ReflectionClassConstant.php b/src/ReflectionClassConstant.php index 7649ae3d..bb14c626 100644 --- a/src/ReflectionClassConstant.php +++ b/src/ReflectionClassConstant.php @@ -200,7 +200,7 @@ public function __toString() ]; $value = $this->getValue(); $type = gettype($value); - if (PHP_VERSION_ID >= 70300 && isset($typeMap[$type])) { + if (isset($typeMap[$type])) { $type = $typeMap[$type]; } $valueType = new ReflectionType($type, null, true); diff --git a/src/ReflectionFunction.php b/src/ReflectionFunction.php index 8813591d..7a3d74c3 100644 --- a/src/ReflectionFunction.php +++ b/src/ReflectionFunction.php @@ -77,11 +77,11 @@ public function getClosure() /** * {@inheritDoc} */ - public function invoke($args = null) + public function invoke(mixed ...$args) { $this->initializeInternalReflection(); - return parent::invoke(...func_get_args()); + return parent::invoke(...$args); } /** diff --git a/src/ReflectionMethod.php b/src/ReflectionMethod.php index 71badc85..49276775 100644 --- a/src/ReflectionMethod.php +++ b/src/ReflectionMethod.php @@ -112,11 +112,10 @@ public function __toString() } return sprintf( - "%sMethod [ %s%s%s %s method %s ] {\n @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n", + "%sMethod [ %s%s%s %s method %s ] {\n @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n", $this->getDocComment() ? $this->getDocComment() . "\n" : '', $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '', $this->isConstructor() ? ', ctor' : '', - $this->isDestructor() ? ', dtor' : '', $this->isFinal() ? ' final' : '', $this->isStatic() ? ' static' : '', $this->isAbstract() ? ' abstract' : '', @@ -203,17 +202,17 @@ public function getPrototype() /** * {@inheritDoc} */ - public function invoke($object, $args = null) + public function invoke(?object $object, mixed ...$args): mixed { $this->initializeInternalReflection(); - return parent::invoke(...func_get_args()); + return parent::invoke($object, ...$args); } /** * {@inheritDoc} */ - public function invokeArgs($object, array $args) + public function invokeArgs(?object $object, array $args): mixed { $this->initializeInternalReflection(); diff --git a/src/ReflectionProperty.php b/src/ReflectionProperty.php index 6daad7f9..05ecc556 100644 --- a/src/ReflectionProperty.php +++ b/src/ReflectionProperty.php @@ -113,11 +113,35 @@ public function __debugInfo(): array */ public function __toString() { + $defaultValueDisplay = ''; + + if ($this->isDefault()) { + $this->__initialize(); + $defaultValue = $this->getDefaultValue(); + if (is_string($defaultValue)) { + if (strlen($defaultValue) > 18) { + $defaultValue = substr($defaultValue, 0, 15) . '...'; + } + + $defaultValue = "'" . $defaultValue . "'"; + } + + if ($defaultValue === null) { + $defaultValue = "NULL"; + } + + if (is_array($defaultValue)) { + $defaultValueDisplay = '= Array'; + } else { + $defaultValueDisplay = '= ' . $defaultValue; + } + } + return sprintf( - "Property [%s %s $%s ]\n", - $this->isStatic() ? '' : ($this->isDefault() ? ' ' : ' '), + "Property [ %s $%s %s ]\n", implode(' ', Reflection::getModifierNames($this->getModifiers())), - $this->getName() + $this->getName(), + $defaultValueDisplay ); } diff --git a/src/ReflectionType.php b/src/ReflectionType.php index 5e13d7a1..fd4572de 100644 --- a/src/ReflectionType.php +++ b/src/ReflectionType.php @@ -14,6 +14,7 @@ use ReflectionNamedType; use ReflectionType as BaseReflectionType; +use ReflectionUnionType; /** * ReflectionType implementation @@ -92,8 +93,8 @@ public static function convertToDisplayType(BaseReflectionType $type) $displayType = ltrim($displayType, '\\'); - if ($type->allowsNull()) { - $displayType .= ' or NULL'; + if ($type->allowsNull() && ! $type instanceof ReflectionUnionType) { + $displayType = '?' . $displayType; } return $displayType; diff --git a/src/Traits/ReflectionClassLikeTrait.php b/src/Traits/ReflectionClassLikeTrait.php index 200c88e3..5e1b6dad 100644 --- a/src/Traits/ReflectionClassLikeTrait.php +++ b/src/Traits/ReflectionClassLikeTrait.php @@ -221,7 +221,7 @@ public function getConstant($name) /** * {@inheritDoc} */ - public function getConstants() + public function getConstants(?int $filter = null): array { if (!isset($this->constants)) { $this->constants = $this->recursiveCollect( @@ -417,10 +417,6 @@ public function getModifiers() $modifiers += \ReflectionClass::IS_FINAL; } - if (PHP_VERSION_ID < 70000 && $this->isTrait()) { - $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT; - } - if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) { $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT; } @@ -553,7 +549,7 @@ public function getReflectionConstant($name) /** * @inheritDoc */ - public function getReflectionConstants() + public function getReflectionConstants(?int $filter = null): array { if (!isset($this->classConstants)) { $directClassConstants = ReflectionClassConstant::collectFromClassNode( @@ -965,12 +961,12 @@ public function newInstanceArgs(array $args = []) * * @return object */ - public function newInstanceWithoutConstructor($args = null) + public function newInstanceWithoutConstructor() { $function = __FUNCTION__; $this->initializeInternalReflection(); - return parent::$function($args); + return parent::$function(); } /** diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 36335a45..c05cc145 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -71,16 +71,9 @@ public function testCoverAllMethods() public function getFilesToAnalyze() { $files = ['PHP5.5' => [__DIR__ . '/Stub/FileWithClasses55.php']]; - - if (PHP_VERSION_ID >= 50600) { - $files['PHP5.6'] = [__DIR__ . '/Stub/FileWithClasses56.php']; - } - if (PHP_VERSION_ID >= 70000) { - $files['PHP7.0'] = [__DIR__ . '/Stub/FileWithClasses70.php']; - } - if (PHP_VERSION_ID >= 70100) { - $files['PHP7.1'] = [__DIR__ . '/Stub/FileWithClasses71.php']; - } + $files['PHP5.6'] = [__DIR__ . '/Stub/FileWithClasses56.php']; + $files['PHP7.0'] = [__DIR__ . '/Stub/FileWithClasses70.php']; + $files['PHP7.1'] = [__DIR__ . '/Stub/FileWithClasses71.php']; return $files; } diff --git a/tests/ReflectionClassTest.php b/tests/ReflectionClassTest.php index 0b0e4c74..8e51057e 100644 --- a/tests/ReflectionClassTest.php +++ b/tests/ReflectionClassTest.php @@ -30,7 +30,7 @@ class ReflectionClassTest extends AbstractTestCase */ public function testGetModifiers($fileName) { - if (PHP_VERSION_ID >= 70400) { + if (PHP_VERSION_ID >= 80000) { $this->markTestSkipped('TODO: Fix mapping and logic of modifiers'); } $mask = diff --git a/tests/ReflectionFunctionTest.php b/tests/ReflectionFunctionTest.php index a34ec96a..1239983f 100644 --- a/tests/ReflectionFunctionTest.php +++ b/tests/ReflectionFunctionTest.php @@ -34,9 +34,7 @@ public function testGeneralInfoGetters() 'returnsReference', 'getClosureScopeClass', 'getClosureThis' ]; - if (PHP_VERSION_ID >= 70000) { - $allNameGetters[] = 'hasReturnType'; - } + $allNameGetters[] = 'hasReturnType'; foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getFunctions() as $refFunction) { @@ -72,7 +70,7 @@ public function testCoverAllMethods() } if ($allMissedMethods) { - $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); + $this->markTestIncomplete('Methods ' . implode(', ', $allMissedMethods) . ' are not implemented'); } } @@ -105,10 +103,6 @@ public function testInvokeArgsMethod() public function testGetReturnTypeMethod() { - if (PHP_VERSION_ID < 70000) { - $this->markTestSkipped('Test available only for PHP7.0 and newer'); - } - $fileName = stream_resolve_include_path(__DIR__ . self::STUB_FILE70); $reflectionFile = new ReflectionFile($fileName); @@ -129,12 +123,7 @@ public function testGetReturnTypeMethod() $originalReturnType = $originalRefFunction->getReturnType(); $this->assertSame($originalReturnType->allowsNull(), $parsedReturnType->allowsNull()); $this->assertSame($originalReturnType->isBuiltin(), $parsedReturnType->isBuiltin()); - // TODO: To prevent deprecation error in tests - if (PHP_VERSION_ID < 70400) { - $this->assertSame($originalReturnType->__toString(), $parsedReturnType->__toString()); - } else { - $this->assertSame($originalReturnType->getName(), $parsedReturnType->__toString()); - } + $this->assertSame($originalReturnType->getName(), $parsedReturnType->__toString()); } else { $this->assertSame( $originalRefFunction->getReturnType(), diff --git a/tests/ReflectionMethodTest.php b/tests/ReflectionMethodTest.php index 08fe2456..9d84b183 100644 --- a/tests/ReflectionMethodTest.php +++ b/tests/ReflectionMethodTest.php @@ -146,14 +146,9 @@ protected function getGettersToCheck() 'getNumberOfRequiredParameters', 'returnsReference', 'getClosureScopeClass', 'getClosureThis' ]; - if (PHP_VERSION_ID >= 50600) { - $allNameGetters[] = 'isVariadic'; - $allNameGetters[] = 'isGenerator'; - } - - if (PHP_VERSION_ID >= 70000) { - $allNameGetters[] = 'hasReturnType'; - } + $allNameGetters[] = 'isVariadic'; + $allNameGetters[] = 'isGenerator'; + $allNameGetters[] = 'hasReturnType'; return $allNameGetters; } diff --git a/tests/ReflectionParameterTest.php b/tests/ReflectionParameterTest.php index 7f142bdc..5210fb7a 100644 --- a/tests/ReflectionParameterTest.php +++ b/tests/ReflectionParameterTest.php @@ -34,12 +34,8 @@ public function testGeneralInfoGetters($fileName) $onlyWithDefaultValues = array_flip([ 'getDefaultValue', 'getDefaultValueConstantName', 'isDefaultValueConstant' ]); - if (PHP_VERSION_ID >= 50600) { - $allNameGetters[] = 'isVariadic'; - } - if (PHP_VERSION_ID >= 70000) { - $allNameGetters[] = 'hasType'; - } + $allNameGetters[] = 'isVariadic'; + $allNameGetters[] = 'hasType'; foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getFunctions() as $refFunction) { @@ -75,13 +71,8 @@ public function testGeneralInfoGetters($fileName) public function fileProvider() { $files = ['PHP5.5' => [__DIR__ . '/Stub/FileWithParameters55.php']]; - - if (PHP_VERSION_ID >= 50600) { - $files['PHP5.6'] = [__DIR__ . '/Stub/FileWithParameters56.php']; - } - if (PHP_VERSION_ID >= 70000) { - $files['PHP7.0'] = [__DIR__ . '/Stub/FileWithParameters70.php']; - } + $files['PHP5.6'] = [__DIR__ . '/Stub/FileWithParameters56.php']; + $files['PHP7.0'] = [__DIR__ . '/Stub/FileWithParameters70.php']; return $files; } @@ -253,15 +244,12 @@ public function testCoverAllMethods() } if ($allMissedMethods) { - $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); + $this->markTestIncomplete('Methods ' . implode(', ', $allMissedMethods) . ' are not implemented'); } } public function testGetTypeMethod() { - if (PHP_VERSION_ID < 70000) { - $this->markTestSkipped('Test available only for PHP7.0 and newer'); - } $this->setUpFile(__DIR__ . '/Stub/FileWithParameters70.php'); foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { @@ -282,12 +270,7 @@ public function testGetTypeMethod() $originalReturnType = $originalRefParameter->getType(); $this->assertSame($originalReturnType->allowsNull(), $parsedReturnType->allowsNull(), $message); $this->assertSame($originalReturnType->isBuiltin(), $parsedReturnType->isBuiltin(), $message); - // TODO: To prevent deprecation error in tests - if (PHP_VERSION_ID < 70400) { - $this->assertSame($originalReturnType->__toString(), $parsedReturnType->__toString(), $message); - } else { - $this->assertSame($originalReturnType->getName(), $parsedReturnType->__toString(), $message); - } + $this->assertSame($originalReturnType->getName(), $parsedReturnType->__toString(), $message); } else { $this->assertSame( $originalRefParameter->getType(), diff --git a/tests/ReflectionTypeTest.php b/tests/ReflectionTypeTest.php index 79910567..e22e5ea4 100644 --- a/tests/ReflectionTypeTest.php +++ b/tests/ReflectionTypeTest.php @@ -9,12 +9,9 @@ class ReflectionTypeTest extends TestCase { protected function setUp(): void { - if (PHP_VERSION_ID >= 70000) { - include_once (__DIR__ . '/Stub/FileWithClasses70.php'); - } - if (PHP_VERSION_ID >= 70100) { - include_once (__DIR__ . '/Stub/FileWithClasses71.php'); - } + include_once (__DIR__ . '/Stub/FileWithClasses70.php'); + include_once (__DIR__ . '/Stub/FileWithClasses71.php'); + include_once (__DIR__ . '/Stub/FileWithClasses80.php'); } /** @@ -32,12 +29,7 @@ public function testTypeConvertToDisplayTypeWithNativeType() $this->assertCount(2, $nativeParamRefArr); $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); - // TODO: To prevent deprecation error in tests - if (PHP_VERSION_ID < 70400) { - $this->assertEquals('string', (string)$nativeTypeRef); - } else { - $this->assertEquals('string', $nativeTypeRef->getName()); - } + $this->assertEquals('string', $nativeTypeRef->getName()); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); $this->assertEquals('string', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); @@ -58,15 +50,10 @@ public function testTypeConvertToDisplayTypeWithNullableNativeType() $this->assertCount(2, $nativeParamRefArr); $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); - // TODO: To prevent deprecation error in tests - if (PHP_VERSION_ID < 70400) { - $this->assertEquals('string', (string)$nativeTypeRef); - } else { - $this->assertEquals('string', $nativeTypeRef->getName()); - } + $this->assertEquals('string', $nativeTypeRef->getName()); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); - $this->assertEquals('string or NULL', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); + $this->assertEquals('?string', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); } /** @@ -85,14 +72,31 @@ public function testTypeConvertToDisplayTypeImplicitlyNullable() $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); $this->assertTrue($nativeTypeRef->allowsNull()); - // TODO: To prevent deprecation error in tests - if (PHP_VERSION_ID < 70400) { - $this->assertEquals('string', (string)$nativeTypeRef); - } else { - $this->assertEquals('string', $nativeTypeRef->getName()); - } + $this->assertEquals('string', $nativeTypeRef->getName()); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); - $this->assertEquals('string or NULL', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); + $this->assertEquals('?string', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); + } + + /** + * Testing convertToDisplayType() with native \ReflectionType + * + * We're already testing it with Go\ParserReflection\ReflectionType + * elsewhere. + */ + public function testTypeConvertToDisplayTypeImplicitlyUnionNullable() + { + $nativeClassRef = new \ReflectionClass('Go\\ParserReflection\\Stub\\ClassWithPhp80Features'); + $nativeMethodRef = $nativeClassRef->getMethod('acceptsStringArrayDefaultToNull'); + $this->assertEquals(\ReflectionMethod::class, get_class($nativeMethodRef)); + $nativeParamRefArr = $nativeMethodRef->getParameters(); + $this->assertCount(1, $nativeParamRefArr); + $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); + $nativeTypeRef = $nativeParamRefArr[0]->getType(); + $this->assertTrue($nativeTypeRef->allowsNull()); + $this->assertEquals('array|string|null', $nativeTypeRef); + $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); + $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); + $this->assertEquals('array|string|null', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); } } diff --git a/tests/Stub/FileWithClasses80.php b/tests/Stub/FileWithClasses80.php new file mode 100644 index 00000000..9784aa27 --- /dev/null +++ b/tests/Stub/FileWithClasses80.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the license that is bundled + * with this source code in the file LICENSE. + */ +declare(strict_types=1); + +namespace Go\ParserReflection\Stub; + +use Go\ParserReflection\{ReflectionMethod, ReflectionProperty as P}; + +class ClassWithPhp80Features +{ + public function acceptsStringArrayDefaultToNull(array|string $iterable = null) : array {} +}