diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index e8695e1e79..226d6853fa 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -652,7 +652,7 @@ protected function denormalizeObjectCollection(string $attribute, ApiProperty $p $collectionKeyType = $type->getCollectionKeyType(); while ($collectionKeyType instanceof WrappingTypeInterface) { - $collectionKeyType = $type->getWrappedType(); + $collectionKeyType = $collectionKeyType->getWrappedType(); } if (!$collectionKeyType->accepts($index)) { diff --git a/src/Serializer/Tests/AbstractItemNormalizerTest.php b/src/Serializer/Tests/AbstractItemNormalizerTest.php index 3a44cbd322..3a1b7e1273 100644 --- a/src/Serializer/Tests/AbstractItemNormalizerTest.php +++ b/src/Serializer/Tests/AbstractItemNormalizerTest.php @@ -1936,6 +1936,46 @@ public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void $normalizer->denormalize($data, Dummy::class, 'xml'); } + public function testDenormalizeCollectionWithTemplateKeyType(): void + { + $data = [ + 'relatedDummies' => [ + ['name' => 'foo'], + ], + ]; + + $relatedDummy = new RelatedDummy(); + $relatedDummy->setName('foo'); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies'])); + + $relatedDummiesType = Type::array(Type::object(RelatedDummy::class), Type::template('TKey', Type::arrayKey())); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummies', [$relatedDummy])->shouldBeCalled(); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class); + $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class); + $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); + $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(DenormalizerInterface::class); + $serializerProphecy->denormalize(['name' => 'foo'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy); + + $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {}; + $normalizer->setSerializer($serializerProphecy->reveal()); + + $normalizer->denormalize($data, Dummy::class); + } + public function testDenormalizePopulatingNonCloneableObject(): void { $dummy = new NonCloneableDummy();