From f897a7b2b657495e4d713790a81b258f9bc842be Mon Sep 17 00:00:00 2001 From: valtzu Date: Fri, 13 Sep 2024 19:09:13 +0300 Subject: [PATCH] Fix `TemplateType` handling in `AbstractObjectNormalizer` --- .../AbstractObjectNormalizerTest.php | 76 +++++++++++++++++++ .../Component/TypeInfo/Type/TemplateType.php | 3 +- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index a666185dd6a99..26f9be4ad714e 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type as LegacyType; @@ -37,6 +38,7 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; @@ -1247,6 +1249,52 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string $this->assertInstanceOf(\ArrayObject::class, $actual->foo); $this->assertSame(1, $actual->foo->count()); } + + public function testTemplateTypeWhenAnObjectIsPassedToDenormalize() + { + $normalizer = new class ( + classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), + propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) + ) extends AbstractObjectNormalizerDummy { + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + { + return true; + } + }; + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $denormalizedData = $normalizer->denormalize(['value' => new DummyGenericsValue()], DummyGenericsValueWrapper::class); + + $this->assertInstanceOf(DummyGenericsValueWrapper::class, $denormalizedData); + $this->assertInstanceOf(DummyGenericsValue::class, $denormalizedData->value); + + $this->assertSame('dummy', $denormalizedData->value->type); + } + + public function testDenormalizeTemplateType() + { + $normalizer = new class ( + classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), + propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) + ) extends AbstractObjectNormalizerDummy { + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + { + return true; + } + }; + $serializer = new Serializer([new ArrayDenormalizer(), $normalizer]); + $normalizer->setSerializer($serializer); + + $denormalizedData = $normalizer->denormalize(['value' => ['type' => 'dummy'], 'values' => [['type' => 'dummy']]], DummyGenericsValueWrapper::class); + + $this->assertInstanceOf(DummyGenericsValueWrapper::class, $denormalizedData); + $this->assertInstanceOf(DummyGenericsValue::class, $denormalizedData->value); + $this->assertContainsOnlyInstancesOf(DummyGenericsValue::class, $denormalizedData->values); + $this->assertCount(1, $denormalizedData->values); + $this->assertSame('dummy', $denormalizedData->value->type); + $this->assertSame('dummy', $denormalizedData->values[0]->type); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1753,3 +1801,31 @@ public function getSupportedTypes(?string $format): array ]; } } + +#[DiscriminatorMap('type', ['dummy' => DummyGenericsValue::class])] +abstract class AbstractDummyGenericsValue +{ + public function __construct( + public string $type, + ) { + } +} + +class DummyGenericsValue extends AbstractDummyGenericsValue +{ + public function __construct() + { + parent::__construct('dummy'); + } +} + +/** + * @template T of AbstractDummyGenericsValue + */ +class DummyGenericsValueWrapper +{ + /** @var T */ + public mixed $value; + /** @var T[] */ + public array $values; +} diff --git a/src/Symfony/Component/TypeInfo/Type/TemplateType.php b/src/Symfony/Component/TypeInfo/Type/TemplateType.php index 10a3a909fc0a6..902dd89a87ef3 100644 --- a/src/Symfony/Component/TypeInfo/Type/TemplateType.php +++ b/src/Symfony/Component/TypeInfo/Type/TemplateType.php @@ -11,7 +11,6 @@ namespace Symfony\Component\TypeInfo\Type; -use Symfony\Component\TypeInfo\Exception\LogicException; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeIdentifier; @@ -33,7 +32,7 @@ public function __construct( public function getBaseType(): BuiltinType|ObjectType { - throw new LogicException(sprintf('Cannot get base type on "%s" template type.', $this)); + return $this->bound->getBaseType(); } public function isA(TypeIdentifier|string $subject): bool