From 6ab0182bbff4cd4dd135e842b8ee71bbcd0cc10b Mon Sep 17 00:00:00 2001 From: Korvin Szanto Date: Mon, 28 Apr 2025 14:49:34 -0700 Subject: [PATCH] [Serializer] Handle invalid mapping type property type --- .../Normalizer/AbstractObjectNormalizer.php | 4 ++ .../Attributes/AbstractDummyFirstChild.php | 1 + .../AbstractObjectNormalizerTest.php | 67 +++++++++++++++++++ .../Serializer/Tests/SerializerTest.php | 2 +- 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 00d2e3b00ef83..2129e4aac8fed 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -1159,6 +1159,10 @@ private function getMappedClass(array $data, string $class, array $context): str 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); } + if (!\is_string($type) && (!\is_object($type) || !method_exists($type, '__toString'))) { + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type property "%s" for the abstract object "%s" must be a string or a stringable object.', $mapping->getTypeProperty(), $class), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); + } + if (null === $mappedClass = $mapping->getClassForType($type)) { throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true); } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummyFirstChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummyFirstChild.php index b5cd2faba24ee..cb9cb0abbe347 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummyFirstChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummyFirstChild.php @@ -16,6 +16,7 @@ class AbstractDummyFirstChild extends AbstractDummy { public $bar; + public $baz; /** @var DummyFirstChildQuux|null */ public $quux; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 270b65f33ef66..cae7da48ba93e 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -525,6 +525,73 @@ private function getDenormalizerForStringCollection() return $denormalizer; } + /** + * @dataProvider provideInvalidDiscriminatorTypes + */ + public function testDenormalizeWithDiscriminatorMapHandlesInvalidTypeValue(mixed $typeValue, bool $shouldFail) + { + if ($shouldFail) { + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage( + 'The type property "type" for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy" must be a string or a stringable object.' + ); + } + + $factory = new ClassMetadataFactory(new AttributeLoader()); + + $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 ReflectionExtractor(), $discriminatorResolver); + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + $normalizedData = $normalizer->denormalize(['foo' => 'foo', 'baz' => 'baz', 'quux' => ['value' => 'quux'], 'type' => $typeValue], AbstractDummy::class); + + $this->assertInstanceOf(DummyFirstChildQuux::class, $normalizedData->quux); + } + + /** + * @return iterable + */ + public static function provideInvalidDiscriminatorTypes(): array + { + $toStringObject = new class { + public function __toString() + { + return 'first'; + } + }; + + return [ + [[], true], + [new \stdClass(), true], + [123, true], + [false, true], + ['first', false], + [$toStringObject, false], + ]; + } + public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname() { $factory = new ClassMetadataFactory(new AttributeLoader()); diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 8fd0ff8850f63..776a26fa033bc 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -452,7 +452,7 @@ public function hasMetadataFor($value): bool $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PhpDocExtractor(), $discriminatorResolver)], ['json' => new JsonEncoder()]); - $jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","foo":"foo-value"}'; + $jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","baz":null,"foo":"foo-value"}'; $deserialized = $serializer->deserialize($jsonData, AbstractDummy::class, 'json'); $this->assertEquals($example, $deserialized); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy