From 0b0e7bb94d7cc70f80f317ed8460e3d3940e4bac Mon Sep 17 00:00:00 2001
From: Kevin <kevinsliedrecht2000@gmail.com>
Date: Tue, 30 Jul 2024 14:11:16 +0200
Subject: [PATCH 1/3] Fix issue with onUpdated not triggered on nested array
 proprety

---
 src/LiveComponent/src/LiveComponentHydrator.php |  3 ++-
 src/LiveComponent/src/Util/DehydratedProps.php  | 11 +++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php
index 547106fadc3..3cdb2ceda91 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -663,7 +663,8 @@ private function processOnUpdatedHook(object $component, string $frontendName, L
 
         foreach ($onUpdated as $propName => $funcName) {
             if (LiveProp::IDENTITY === $propName) {
-                if (!$dehydratedUpdatedProps->hasPropValue($frontendName)) {
+                if (!$dehydratedUpdatedProps->hasPropValue($frontendName) &&
+                    !$dehydratedUpdatedProps->hasNestedPathsForProperty($frontendName)) {
                     continue;
                 }
 
diff --git a/src/LiveComponent/src/Util/DehydratedProps.php b/src/LiveComponent/src/Util/DehydratedProps.php
index 64d50cea770..693c24b2369 100644
--- a/src/LiveComponent/src/Util/DehydratedProps.php
+++ b/src/LiveComponent/src/Util/DehydratedProps.php
@@ -113,6 +113,17 @@ public function getNestedPathsForProperty(string $prop): array
         return $nestedPaths;
     }
 
+    public function hasNestedPathsForProperty(string $prop): bool
+    {
+        foreach ($this->propValues as $fullPath => $value) {
+            if (str_starts_with($fullPath, $prop.'.')) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     public function calculateUnexpectedNestedPathsForProperty(string $prop, array $expectedNestedPaths): array
     {
         $clone = clone $this;

From b968810566d51395b7fcc1a07b422fb23ca4a8c8 Mon Sep 17 00:00:00 2001
From: Kevin <kevinsliedrecht2000@gmail.com>
Date: Wed, 31 Jul 2024 09:08:22 +0200
Subject: [PATCH 2/3] Add onUpdated with array test to
 LiveComponentHydratorTest

---
 .../src/LiveComponentHydrator.php             |  4 ++--
 .../Integration/LiveComponentHydratorTest.php | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php
index 3cdb2ceda91..bd022ccb577 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -663,8 +663,8 @@ private function processOnUpdatedHook(object $component, string $frontendName, L
 
         foreach ($onUpdated as $propName => $funcName) {
             if (LiveProp::IDENTITY === $propName) {
-                if (!$dehydratedUpdatedProps->hasPropValue($frontendName) &&
-                    !$dehydratedUpdatedProps->hasNestedPathsForProperty($frontendName)) {
+                if (!$dehydratedUpdatedProps->hasPropValue($frontendName)
+                    && !$dehydratedUpdatedProps->hasNestedPathsForProperty($frontendName)) {
                     continue;
                 }
 
diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
index 2441146fccd..119cbfd9beb 100644
--- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
+++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
@@ -234,6 +234,25 @@ public function onEntireEntityUpdated($oldValue)
                 });
         }];
 
+        yield 'onUpdated: with array' => [function () {
+            return HydrationTest::create(new class() {
+                #[LiveProp(writable: true, onUpdated: 'onNestedArrayUpdated')]
+                public array $array = [];
+
+                public function onNestedArrayUpdated($oldValue)
+                {
+                    if ('Kevin' === $this->array['name']) {
+                        $this->array['name'] = 'Simon';
+                    }
+                }
+            })
+                ->mountWith(['array' => ['name' => 'Ryan']])
+                ->userUpdatesProps(['array.name' => 'Kevin'])
+                ->assertObjectAfterHydration(function (object $object) {
+                    self::assertSame('Simon', $object->array['name']);
+                });
+        }];
+
         yield 'string: (de)hydrates correctly' => [function () {
             return HydrationTest::create(new class() {
                 #[LiveProp()]

From 6c46e0c284fdcd4edc65bb89e2adbd41dd91b1a2 Mon Sep 17 00:00:00 2001
From: Kevin <kevinsliedrecht2000@gmail.com>
Date: Thu, 1 Aug 2024 14:45:24 +0200
Subject: [PATCH 3/3] Add support for wildcard to onUpdated

---
 src/LiveComponent/src/LiveComponentHydrator.php   | 15 +++++++--------
 src/LiveComponent/src/Util/DehydratedProps.php    | 10 +++-------
 .../Integration/LiveComponentHydratorTest.php     | 10 ++++++----
 3 files changed, 16 insertions(+), 19 deletions(-)

diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php
index 061f7b7afd4..88a788ba6b0 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -670,8 +670,7 @@ private function processOnUpdatedHook(object $component, string $frontendName, L
 
         foreach ($onUpdated as $propName => $funcName) {
             if (LiveProp::IDENTITY === $propName) {
-                if (!$dehydratedUpdatedProps->hasPropValue($frontendName)
-                    && !$dehydratedUpdatedProps->hasNestedPathsForProperty($frontendName)) {
+                if (!$dehydratedUpdatedProps->hasPropValue($frontendName)) {
                     continue;
                 }
 
@@ -687,13 +686,13 @@ private function processOnUpdatedHook(object $component, string $frontendName, L
             }
 
             $key = \sprintf('%s.%s', $frontendName, $propName);
-            if (!$dehydratedUpdatedProps->hasPropValue($key)) {
-                continue;
-            }
+            $fullPaths = $dehydratedUpdatedProps->searchFullPathsForProperty($key);
 
-            $this->ensureOnUpdatedMethodExists($component, $funcName);
-            $propertyOldValue = $dehydratedOriginalProps->getPropValue($key);
-            $component->{$funcName}($propertyOldValue);
+            foreach ($fullPaths as $fullPath) {
+                $this->ensureOnUpdatedMethodExists($component, $funcName);
+                $propertyOldValue = $dehydratedOriginalProps->getPropValue($fullPath);
+                $component->{$funcName}($propertyOldValue);
+            }
         }
     }
 }
diff --git a/src/LiveComponent/src/Util/DehydratedProps.php b/src/LiveComponent/src/Util/DehydratedProps.php
index 693c24b2369..9c8f63a20ab 100644
--- a/src/LiveComponent/src/Util/DehydratedProps.php
+++ b/src/LiveComponent/src/Util/DehydratedProps.php
@@ -113,15 +113,11 @@ public function getNestedPathsForProperty(string $prop): array
         return $nestedPaths;
     }
 
-    public function hasNestedPathsForProperty(string $prop): bool
+    public function searchFullPathsForProperty(string $prop): array
     {
-        foreach ($this->propValues as $fullPath => $value) {
-            if (str_starts_with($fullPath, $prop.'.')) {
-                return true;
-            }
-        }
+        $regex = '/^'.preg_replace(['/\\\\\*$/i', '/\\\\\*/i'], ['', '.*?'], preg_quote($prop, '/')).'/i';
 
-        return false;
+        return preg_grep($regex, array_keys($this->propValues));
     }
 
     public function calculateUnexpectedNestedPathsForProperty(string $prop, array $expectedNestedPaths): array
diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
index 119cbfd9beb..1f057c693d9 100644
--- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
+++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
@@ -234,12 +234,12 @@ public function onEntireEntityUpdated($oldValue)
                 });
         }];
 
-        yield 'onUpdated: with array' => [function () {
+        yield 'onUpdated: with wildcard' => [function () {
             return HydrationTest::create(new class() {
-                #[LiveProp(writable: true, onUpdated: 'onNestedArrayUpdated')]
+                #[LiveProp(writable: true, onUpdated: ['*' => 'onArrayUpdated'])]
                 public array $array = [];
 
-                public function onNestedArrayUpdated($oldValue)
+                public function onArrayUpdated($oldValue)
                 {
                     if ('Kevin' === $this->array['name']) {
                         $this->array['name'] = 'Simon';
@@ -247,7 +247,9 @@ public function onNestedArrayUpdated($oldValue)
                 }
             })
                 ->mountWith(['array' => ['name' => 'Ryan']])
-                ->userUpdatesProps(['array.name' => 'Kevin'])
+                ->userUpdatesProps([
+                    'array.name' => 'Kevin',
+                ])
                 ->assertObjectAfterHydration(function (object $object) {
                     self::assertSame('Simon', $object->array['name']);
                 });