diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index d03c9dd819..b62dd25a75 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -133,6 +133,18 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp */ public function getTypes(string $class, string $property, array $context = []): ?array { + if (\PHP_VERSION_ID >= 70400) { + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + $type = $reflectionProperty->getType(); + if (null !== $type) { + return [$this->extractFromReflectionType($type, $reflectionProperty->getDeclaringClass())]; + } + } catch (\ReflectionException $e) { + // noop + } + } + if ($fromMutator = $this->extractFromMutator($class, $property)) { return $fromMutator; } @@ -227,7 +239,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp if (!$reflectionType = $reflectionParameter->getType()) { return null; } - $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod); + $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); if (\in_array($prefix, $this->arrayMutatorPrefixes)) { $type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type); @@ -249,7 +261,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp } if ($reflectionType = $reflectionMethod->getReturnType()) { - return [$this->extractFromReflectionType($reflectionType, $reflectionMethod)]; + return [$this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass())]; } if (\in_array($prefix, ['is', 'can', 'has'])) { @@ -284,7 +296,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp } $reflectionType = $parameter->getType(); - return $reflectionType ? [$this->extractFromReflectionType($reflectionType, $constructor)] : null; + return $reflectionType ? [$this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass())] : null; } if ($parentClass = $reflectionClass->getParentClass()) { @@ -313,7 +325,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp return [new Type(static::MAP_TYPES[$type] ?? $type)]; } - private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod): Type + private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): Type { $phpTypeOrClass = $reflectionType->getName(); $nullable = $reflectionType->allowsNull(); @@ -325,18 +337,18 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp } elseif ($reflectionType->isBuiltin()) { $type = new Type($phpTypeOrClass, $nullable); } else { - $type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod)); + $type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass)); } return $type; } - private function resolveTypeName(string $name, \ReflectionMethod $reflectionMethod): string + private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string { if ('self' === $lcName = strtolower($name)) { - return $reflectionMethod->getDeclaringClass()->name; + return $declaringClass->name; } - if ('parent' === $lcName && $parent = $reflectionMethod->getDeclaringClass()->getParentClass()) { + if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) { return $parent->name; } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 45fd42c39a..cf26b49b84 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -19,6 +19,7 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2; +use Symfony\Component\PropertyInfo\Tests\Fixtures\Php74Dummy; use Symfony\Component\PropertyInfo\Type; /** @@ -365,4 +366,13 @@ class ReflectionExtractorTest extends TestCase [DefaultValue::class, 'foo', null], ]; } + + /** + * @requires PHP 7.4 + */ + public function testTypedProperties(): void + { + $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], $this->extractor->getTypes(Php74Dummy::class, 'dummy')); + $this->assertEquals([new Type(Type::BUILTIN_TYPE_BOOL, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableBoolProp')); + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php74Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php74Dummy.php new file mode 100644 index 0000000000..9d3146442d --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php74Dummy.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +/** + * @author Kévin Dunglas + */ +class Php74Dummy +{ + public Dummy $dummy; + private ?bool $nullableBoolProp; +}