diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md index 7ba0351676c..832c31885f4 100644 --- a/src/LiveComponent/CHANGELOG.md +++ b/src/LiveComponent/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.25.0 + +- Add support for [Symfony UID](https://symfony.com/doc/current/components/uid.html) hydration/dehydration + ## 2.23.0 - Allow configuring the secret used to compute fingerprints and checksums. diff --git a/src/LiveComponent/composer.json b/src/LiveComponent/composer.json index ffa19376e42..1cfdf9b4541 100644 --- a/src/LiveComponent/composer.json +++ b/src/LiveComponent/composer.json @@ -50,6 +50,7 @@ "symfony/security-bundle": "^5.4|^6.0|^7.0", "symfony/serializer": "^5.4|^6.0|^7.0", "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^5.4|^6.0|^7.0", "zenstruck/browser": "^1.2.0", "zenstruck/foundry": "^2.0" diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php index 95f4bf91f9c..ce55e9f873b 100644 --- a/src/LiveComponent/src/LiveComponentHydrator.php +++ b/src/LiveComponent/src/LiveComponentHydrator.php @@ -22,6 +22,7 @@ use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Uid\AbstractUid; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\Exception\HydrationException; @@ -505,6 +506,10 @@ private function dehydrateObjectValue(object $value, string $classType, ?string return $value->value; } + if ($value instanceof AbstractUid) { + return (string) $value; + } + foreach ($this->hydrationExtensions as $extension) { if ($extension->supports($classType)) { return $extension->dehydrate($value); @@ -553,6 +558,14 @@ private function hydrateObjectValue(mixed $value, string $className, bool $allow return new $className($value); } + if (is_a($className, AbstractUid::class, true)) { + if (!\is_string($value)) { + throw new BadRequestHttpException(\sprintf('The model path "%s" was sent an invalid data type "%s" for a uuid.', $propertyPathForError, get_debug_type($value))); + } + + return $className::fromString($value); + } + foreach ($this->hydrationExtensions as $extension) { if ($extension->supports($className)) { return $extension->hydrate($value, $className); diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php index 004a6dff6a1..b9e1f9228e3 100644 --- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php +++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php @@ -13,6 +13,9 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV4; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\Exception\HydrationException; use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadata; @@ -1372,6 +1375,36 @@ public function modifyDateProp(LiveProp $prop): LiveProp }) ; }]; + + yield 'Uuid: (de)hydrates correctly' => [function () { + $uuid = new UuidV4('ffdb229c-13e6-4bc4-939e-c8e73958104c'); + + return HydrationTest::create(new class { + #[LiveProp] + public Uuid $id; + }) + ->mountWith(['id' => $uuid]) + ->assertDehydratesTo(['id' => 'ffdb229c-13e6-4bc4-939e-c8e73958104c']) + ->assertObjectAfterHydration(function (object $object) { + self::assertEquals(new UuidV4('ffdb229c-13e6-4bc4-939e-c8e73958104c'), $object->id); + }) + ; + }]; + + yield 'Ulid: (de)hydrates correctly' => [function () { + $uuid = new Ulid('01AN4Z07BY79KA1307SR9X4MV3'); + + return HydrationTest::create(new class { + #[LiveProp] + public Ulid $id; + }) + ->mountWith(['id' => $uuid]) + ->assertDehydratesTo(['id' => '01AN4Z07BY79KA1307SR9X4MV3']) + ->assertObjectAfterHydration(function (object $object) { + self::assertEquals(new Ulid('01AN4Z07BY79KA1307SR9X4MV3'), $object->id); + }) + ; + }]; } public function testHydrationWithInvalidDate(): void