From ce0860865f1a8cf41d7c93a6b8002e3b17615daa Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 31 May 2023 13:43:23 +0200 Subject: [PATCH] [Serializer] Fix discriminator map not working with `AbstractNormalizer::OBJECT_TO_POPULATE` --- .../Normalizer/AbstractObjectNormalizer.php | 6 ++ .../AbstractObjectNormalizerTest.php | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 8904383dd7d38..7c91171527376 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -263,6 +263,12 @@ private function getAttributeMetadata($objectOrClass, string $attribute): ?Attri */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) { + if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { + unset($context[self::OBJECT_TO_POPULATE]); + + return $object; + } + if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { if (!isset($data[$mapping->getTypeProperty()])) { throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index aa142ed3b4b05..bce6e5f9a598c 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -27,6 +27,7 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; @@ -36,6 +37,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; class AbstractObjectNormalizerTest extends TestCase @@ -252,6 +254,61 @@ public function hasMetadataFor($value): bool $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux); } + public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrectClassname() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $loaderMock = new class() implements ClassMetadataFactoryInterface { + public function getMetadataFor($value): ClassMetadataInterface + { + if (AbstractDummy::class === $value) { + return new ClassMetadata( + AbstractDummy::class, + new ClassDiscriminatorMapping('type', [ + 'first' => AbstractDummyFirstChild::class, + 'second' => AbstractDummySecondChild::class, + ]) + ); + } + + throw new InvalidArgumentException(); + } + + public function hasMetadataFor($value): bool + { + return AbstractDummy::class === $value; + } + }; + + $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); + $normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver); + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $data = [ + 'foo' => 'foo', + 'quux' => ['value' => 'quux'], + ]; + + $normalizedData1 = $normalizer->denormalize($data + ['bar' => 'bar'], AbstractDummy::class, 'any', [ + AbstractNormalizer::OBJECT_TO_POPULATE => new AbstractDummyFirstChild('notfoo', 'notbar'), + ]); + $this->assertInstanceOf(AbstractDummyFirstChild::class, $normalizedData1); + $this->assertSame('foo', $normalizedData1->foo); + $this->assertSame('notbar', $normalizedData1->bar); + $this->assertInstanceOf(DummyFirstChildQuux::class, $normalizedData1->quux); + $this->assertSame('quux', $normalizedData1->quux->getValue()); + + $normalizedData2 = $normalizer->denormalize($data + ['baz' => 'baz'], AbstractDummy::class, 'any', [ + AbstractNormalizer::OBJECT_TO_POPULATE => new AbstractDummySecondChild('notfoo', 'notbaz'), + ]); + $this->assertInstanceOf(AbstractDummySecondChild::class, $normalizedData2); + $this->assertSame('foo', $normalizedData2->foo); + $this->assertSame('baz', $normalizedData2->baz); + $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData2->quux); + $this->assertSame('quux', $normalizedData2->quux->getValue()); + } + public function testDenormalizeWithNestedDiscriminatorMap() { $classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface {