bug #40699 [PropertyInfo] Make ReflectionExtractor correctly extract nullability (shiftby)

This PR was squashed before being merged into the 4.4 branch.

Discussion
----------

[PropertyInfo] Make ReflectionExtractor correctly extract nullability

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #40659
| License       | MIT
| Doc PR        | no

When the property had a default value ReflectionExtractor was always returning isNullable: false. After PHP 7.4 we can get isNullable from the typehint.

Commits
-------

d5fce4c779 [PropertyInfo] Make ReflectionExtractor correctly extract nullability
This commit is contained in:
Nicolas Grekas 2021-05-07 15:22:49 +02:00
commit fab61ee9df
3 changed files with 34 additions and 16 deletions

View File

@ -154,20 +154,8 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
return $fromConstructor; return $fromConstructor;
} }
if ($fromDefaultValue = $this->extractFromDefaultValue($class, $property)) { if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) {
return $fromDefaultValue; return $fromPropertyDeclaration;
}
if (\PHP_VERSION_ID >= 70400) {
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
$type = $reflectionProperty->getType();
if (null !== $type && $types = $this->extractFromReflectionType($type, $reflectionProperty->getDeclaringClass())) {
return $types;
}
} catch (\ReflectionException $e) {
// noop
}
} }
return null; return null;
@ -312,10 +300,19 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
return null; return null;
} }
private function extractFromDefaultValue(string $class, string $property): ?array private function extractFromPropertyDeclaration(string $class, string $property): ?array
{ {
try { try {
$reflectionClass = new \ReflectionClass($class); $reflectionClass = new \ReflectionClass($class);
if (\PHP_VERSION_ID >= 70400) {
$reflectionProperty = $reflectionClass->getProperty($property);
$reflectionPropertyType = $reflectionProperty->getType();
if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) {
return $types;
}
}
} catch (\ReflectionException $e) { } catch (\ReflectionException $e) {
return null; return null;
} }
@ -328,7 +325,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$type = \gettype($defaultValue); $type = \gettype($defaultValue);
return [new Type(static::MAP_TYPES[$type] ?? $type)]; return [new Type(static::MAP_TYPES[$type] ?? $type, $this->isNullableProperty($class, $property))];
} }
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
@ -368,6 +365,25 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
return $name; return $name;
} }
private function isNullableProperty(string $class, string $property): bool
{
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
if (\PHP_VERSION_ID >= 70400) {
$reflectionPropertyType = $reflectionProperty->getType();
return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
}
return false;
} catch (\ReflectionException $e) {
// Return false if the property doesn't exist
}
return false;
}
private function isAllowedProperty(string $class, string $property): bool private function isAllowedProperty(string $class, string $property): bool
{ {
try { try {

View File

@ -414,5 +414,6 @@ class ReflectionExtractorTest extends TestCase
$this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], $this->extractor->getTypes(Php74Dummy::class, 'dummy')); $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')); $this->assertEquals([new Type(Type::BUILTIN_TYPE_BOOL, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableBoolProp'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], $this->extractor->getTypes(Php74Dummy::class, 'stringCollection')); $this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], $this->extractor->getTypes(Php74Dummy::class, 'stringCollection'));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_INT, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableWithDefault'));
} }
} }

View File

@ -20,6 +20,7 @@ class Php74Dummy
private ?bool $nullableBoolProp; private ?bool $nullableBoolProp;
/** @var string[] */ /** @var string[] */
private array $stringCollection; private array $stringCollection;
private ?int $nullableWithDefault = 1;
public function addStringCollection(string $string): void public function addStringCollection(string $string): void
{ {