diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index f30bc440e7..035a460e77 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -139,6 +139,18 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp */ public function getTypes($class, $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; } @@ -233,7 +245,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp if (!$reflectionType = $reflectionParameter->getType()) { return null; } - $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod); + $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) { $type = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])]; @@ -255,7 +267,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'])) { @@ -290,7 +302,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()) { @@ -319,7 +331,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp return [new Type(static::MAP_TYPES[$type] ?? $type)]; } - private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod): array + private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array { $types = []; $nullable = $reflectionType->allowsNull(); @@ -337,19 +349,19 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp } elseif ($type->isBuiltin()) { $types[] = new Type($phpTypeOrClass, $nullable); } else { - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod)); + $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass)); } } return $types; } - 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 8a72e99e9b..e15574f619 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; /** @@ -389,4 +390,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; +}