From 32dc13432fea4636ffff54ff5876e07139922450 Mon Sep 17 00:00:00 2001 From: Antonio Pauletich Date: Sat, 12 Aug 2023 17:11:13 +0200 Subject: [PATCH] [Serializer] Fix deserializing object collection properties --- .../Component/Serializer/Serializer.php | 3 ++ .../Tests/Fixtures/FooDummyInterface.php | 16 ++++++ .../Tests/Fixtures/FooImplementationDummy.php | 20 ++++++++ .../FooInterfaceDummyDenormalizer.php | 50 +++++++++++++++++++ .../ObjectCollectionPropertyDummy.php | 25 ++++++++++ .../Serializer/Tests/SerializerTest.php | 18 +++++++ 6 files changed, 132 insertions(+) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/FooDummyInterface.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/FooImplementationDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/ObjectCollectionPropertyDummy.php diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index c83da01fbe1f1..0f6bb1dc27ceb 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -359,9 +359,12 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar $supportedTypes = $normalizer->getSupportedTypes($format); + $doesClassRepresentCollection = str_ends_with($class, '[]'); + foreach ($supportedTypes as $supportedType => $isCacheable) { if (\in_array($supportedType, ['*', 'object'], true) || $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType)) + && !($doesClassRepresentCollection && str_ends_with($supportedType, '[]') && is_subclass_of(strstr($class, '[]', true), strstr($supportedType, '[]', true))) ) { continue; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/FooDummyInterface.php b/src/Symfony/Component/Serializer/Tests/Fixtures/FooDummyInterface.php new file mode 100644 index 0000000000000..da206e039ac56 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/FooDummyInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +interface FooDummyInterface +{ +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/FooImplementationDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/FooImplementationDummy.php new file mode 100644 index 0000000000000..b7f7194a5a19a --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/FooImplementationDummy.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +final class FooImplementationDummy implements FooDummyInterface +{ + /** + * @var string + */ + public $name; +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php b/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php new file mode 100644 index 0000000000000..a8c45373b70ee --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + +final class FooInterfaceDummyDenormalizer implements DenormalizerInterface +{ + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array + { + $result = []; + foreach ($data as $foo) { + $fooDummy = new FooImplementationDummy(); + $fooDummy->name = $foo['name']; + $result[] = $fooDummy; + } + + return $result; + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + if (str_ends_with($type, '[]')) { + $className = substr($type, 0, -2); + $classImplements = class_implements($className); + \assert(\is_array($classImplements)); + + return class_exists($className) && \in_array(FooDummyInterface::class, $classImplements, true); + } + + return false; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [FooDummyInterface::class.'[]' => false]; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/ObjectCollectionPropertyDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/ObjectCollectionPropertyDummy.php new file mode 100644 index 0000000000000..cbb77987bd8ab --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/ObjectCollectionPropertyDummy.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +final class ObjectCollectionPropertyDummy +{ + /** + * @var FooImplementationDummy[] + */ + public $foo; + + public function getFoo(): array + { + return $this->foo; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index f4a164dc5b3f1..76deeb5cd05c4 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -58,7 +58,10 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor; use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty; use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy; +use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy; +use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer; use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy; +use Symfony\Component\Serializer\Tests\Fixtures\ObjectCollectionPropertyDummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74Full; use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; @@ -711,6 +714,21 @@ public function testDeserializeInconsistentScalarArray() $serializer->deserialize('["42"]', 'int[]', 'json'); } + public function testDeserializeOnObjectWithObjectCollectionProperty() + { + $serializer = new Serializer([new FooInterfaceDummyDenormalizer(), new ObjectNormalizer(null, null, null, new PhpDocExtractor())], [new JsonEncoder()]); + + $obj = $serializer->deserialize('{"foo":[{"name":"bar"}]}', ObjectCollectionPropertyDummy::class, 'json'); + $this->assertInstanceOf(ObjectCollectionPropertyDummy::class, $obj); + + $fooDummyObjects = $obj->getFoo(); + $this->assertCount(1, $fooDummyObjects); + + $fooDummyObject = $fooDummyObjects[0]; + $this->assertInstanceOf(FooImplementationDummy::class, $fooDummyObject); + $this->assertSame('bar', $fooDummyObject->name); + } + public function testDeserializeWrappedScalar() { $serializer = new Serializer([new UnwrappingDenormalizer()], ['json' => new JsonEncoder()]);