Repository: symfony/ux
Pull request #2654 (2025-03-24 08:34:14)

[LiveComponent] Add uid support for hydration
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Docs?         | no
| Issues        | N/A
| License       | MIT

I think it's a common use case, I encountered this many times already..


Patch:
diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md
index 7ba03516..832c3188 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 ffa19376..1cfdf9b4 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 95f4bf91..ce55e9f8 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -22,6 +22,7 @@ use Symfony\Component\PropertyInfo\Type;
 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 @@ final class LiveComponentHydrator
             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 @@ final class LiveComponentHydrator
             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);


Test patch:
diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
index 004a6dff..b9e1f922 100644
--- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
+++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
@@ -13,6 +13,9 @@ namespace Symfony\UX\LiveComponent\Tests\Integration;
 
 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 @@ final class LiveComponentHydratorTest extends KernelTestCase
                 })
             ;
         }];
+
+        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