From 7a8bc4f842129ef8139f88701edf19167bd48506 Mon Sep 17 00:00:00 2001 From: Nicolas Rigaud Date: Sun, 3 Mar 2024 13:24:14 +0100 Subject: [PATCH] [LiveComponent] Fix collections hydration with serializer --- src/LiveComponent/CHANGELOG.md | 1 + .../src/LiveComponentHydrator.php | 15 ++++++-- .../Integration/LiveComponentHydratorTest.php | 36 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md index afdf809d3d7..58e3ffbf542 100644 --- a/src/LiveComponent/CHANGELOG.md +++ b/src/LiveComponent/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.17.0 - Add `modifier` option in `LiveProp` so options can be modified at runtime. +- Fix collections hydration with serializer in LiveComponents ## 2.16.0 diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php index 9adb3ead733..34f9eb427fd 100644 --- a/src/LiveComponent/src/LiveComponentHydrator.php +++ b/src/LiveComponent/src/LiveComponentHydrator.php @@ -256,11 +256,22 @@ public function hydrateValue(mixed $value, LivePropMetadata $propMetadata, objec throw new \LogicException(sprintf('The LiveProp "%s" on component "%s" has "useSerializerForHydration: true", but the given serializer does not implement DenormalizerInterface.', $propMetadata->getName(), $parentObject::class)); } - if (null === $propMetadata->getType()) { + if ($propMetadata->collectionValueType()) { + $builtInType = $propMetadata->collectionValueType()->getBuiltinType(); + if (Type::BUILTIN_TYPE_OBJECT === $builtInType) { + $type = $propMetadata->collectionValueType()->getClassName().'[]'; + } else { + $type = $builtInType.'[]'; + } + } else { + $type = $propMetadata->getType(); + } + + if (null === $type) { throw new \LogicException(sprintf('The "%s::%s" object should be hydrated with the Serializer, but no type could be guessed.', $parentObject::class, $propMetadata->getName())); } - return $this->serializer->denormalize($value, $propMetadata->getType(), 'json', $propMetadata->serializationContext()); + return $this->serializer->denormalize($value, $type, 'json', $propMetadata->serializationContext()); } if ($propMetadata->collectionValueType() && Type::BUILTIN_TYPE_OBJECT === $propMetadata->collectionValueType()->getBuiltinType()) { diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php index 3b57450610a..2bce3486271 100644 --- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php +++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php @@ -1031,6 +1031,42 @@ public function mount() ; }]; + yield 'Collection: using serializer (de)hydrates correctly' => [function () { + return HydrationTest::create(new class() { + /** @var \Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Temperature[] */ + #[LiveProp(useSerializerForHydration: true)] + public array $temperatures = []; + + /** + * @var string[] + */ + #[LiveProp(useSerializerForHydration: true)] + public array $tags = []; + }) + ->mountWith([ + 'temperatures' => [ + new Temperature(10, 'C'), + new Temperature(20, 'C'), + ], + 'tags' => ['foo', 'bar'], + ]) + ->assertDehydratesTo([ + 'temperatures' => [ + ['degrees' => 10, 'uom' => 'C'], + ['degrees' => 20, 'uom' => 'C'], + ], + 'tags' => ['foo', 'bar'], + ]) + ->assertObjectAfterHydration(function (object $object) { + self::assertSame(10, $object->temperatures[0]->degrees); + self::assertSame('C', $object->temperatures[0]->uom); + self::assertSame(20, $object->temperatures[1]->degrees); + self::assertSame('C', $object->temperatures[1]->uom); + self::assertSame(['foo', 'bar'], $object->tags); + }) + ; + }]; + yield 'Updating non-writable path is rejected' => [function () { $product = new ProductFixtureEntity(); $product->name = 'original name';