From ba7c0c5c4c31ae3b016171a14371a3c5702a876f Mon Sep 17 00:00:00 2001 From: David Badura Date: Thu, 20 Feb 2025 15:40:08 +0100 Subject: [PATCH] fix infer normalizer for nullable types --- phpstan-baseline.neon | 6 ----- src/Metadata/AttributeMetadataFactory.php | 11 +++++++++- .../InferNormalizerWithNullableDto.php | 1 + .../Metadata/AttributeMetadataFactoryTest.php | 22 +++++++++++++++++++ tests/Unit/MetadataHydratorTest.php | 22 +++++++++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 21ae4592..388af2f1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24,12 +24,6 @@ parameters: count: 1 path: src/Metadata/AttributeMetadataFactory.php - - - message: '#^Instanceof between Symfony\\Component\\TypeInfo\\Type\\ObjectType and Symfony\\Component\\TypeInfo\\Type\\ObjectType will always evaluate to true\.$#' - identifier: instanceof.alwaysTrue - count: 1 - path: src/Metadata/AttributeMetadataFactory.php - - message: '#^Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class\-string\\|T of object, string given\.$#' identifier: argument.type diff --git a/src/Metadata/AttributeMetadataFactory.php b/src/Metadata/AttributeMetadataFactory.php index 410b43fd..bcec75aa 100644 --- a/src/Metadata/AttributeMetadataFactory.php +++ b/src/Metadata/AttributeMetadataFactory.php @@ -29,6 +29,7 @@ use ReflectionProperty; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\NullableType; use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; @@ -396,13 +397,21 @@ private function findNormalizer(ReflectionProperty $reflectionProperty, Type $ty return $attributeReflectionList[0]->newInstance(); } + if ($type instanceof NullableType) { + $type = $type->getWrappedType(); + } + if ($type instanceof ObjectType) { return $this->findNormalizerOnClass(new ReflectionClass($type->getClassName())); } - if ($type instanceof CollectionType && $type->getCollectionValueType() instanceof ObjectType) { + if ($type instanceof CollectionType) { $valueType = $type->getCollectionValueType(); + if ($valueType instanceof NullableType) { + $valueType = $type->getWrappedType(); + } + if (!$valueType instanceof ObjectType) { return null; } diff --git a/tests/Unit/Fixture/InferNormalizerWithNullableDto.php b/tests/Unit/Fixture/InferNormalizerWithNullableDto.php index 18745ab8..09ab4e5d 100644 --- a/tests/Unit/Fixture/InferNormalizerWithNullableDto.php +++ b/tests/Unit/Fixture/InferNormalizerWithNullableDto.php @@ -15,6 +15,7 @@ public function __construct( public DateTimeImmutable|null $dateTimeImmutable, public DateTime|null $dateTime = null, public DateTimeZone|null $dateTimeZone = null, + public ProfileId|null $profileId = null, ) { } } diff --git a/tests/Unit/Metadata/AttributeMetadataFactoryTest.php b/tests/Unit/Metadata/AttributeMetadataFactoryTest.php index 86b66ce2..5fd52d58 100644 --- a/tests/Unit/Metadata/AttributeMetadataFactoryTest.php +++ b/tests/Unit/Metadata/AttributeMetadataFactoryTest.php @@ -27,6 +27,7 @@ use Patchlevel\Hydrator\Tests\Unit\Fixture\MissingSubjectIdDto; use Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto; use Patchlevel\Hydrator\Tests\Unit\Fixture\ParentWithPersonalDataDto; +use Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileId; use Patchlevel\Hydrator\Tests\Unit\Fixture\Status; use PHPUnit\Framework\TestCase; @@ -199,6 +200,27 @@ public function __construct( self::assertSame(Status::class, $normalizer->getEnum()); } + public function testEventWithInferNormalizer(): void + { + $object = new class { + public function __construct( + public ProfileId|null $profileId = null, + ) { + } + }; + + $metadataFactory = new AttributeMetadataFactory(); + $metadata = $metadataFactory->metadata($object::class); + + $properties = $metadata->properties(); + + self::assertCount(1, $properties); + + $propertyMetadata = $metadata->propertyForField('profileId'); + + self::assertEquals(new IdNormalizer(ProfileId::class), $propertyMetadata->normalizer()); + } + public function testExtends(): void { $metadataFactory = new AttributeMetadataFactory(); diff --git a/tests/Unit/MetadataHydratorTest.php b/tests/Unit/MetadataHydratorTest.php index 01493047..11f5e152 100644 --- a/tests/Unit/MetadataHydratorTest.php +++ b/tests/Unit/MetadataHydratorTest.php @@ -106,6 +106,28 @@ public function testExtractCircularReference(): void $this->hydrator->extract($dto1); } + public function testExtractWithInferNormalizer2(): void + { + $result = $this->hydrator->extract( + new InferNormalizerWithNullableDto( + null, + null, + profileId: ProfileId::fromString('1'), + ), + ); + + self::assertEquals( + [ + 'status' => null, + 'dateTimeImmutable' => null, + 'dateTime' => null, + 'dateTimeZone' => null, + 'profileId' => '1', + ], + $result, + ); + } + public function testExtractWithInferNormalizerFailed(): void { $this->expectException(NormalizationMissing::class);