From 5650ecf77b2e4ceebece45f4f8bea6ca98924051 Mon Sep 17 00:00:00 2001 From: Christoph Grabenstein Date: Mon, 15 Jul 2024 09:50:13 +0200 Subject: [PATCH] Allow updates in arrays of DTOs --- .../src/LiveComponentHydrator.php | 5 + .../tests/Fixtures/Dto/HoldsArrayOfDtos.php | 13 ++ .../Integration/LiveComponentHydratorTest.php | 122 ++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php index 4a713882329..547106fadc3 100644 --- a/src/LiveComponent/src/LiveComponentHydrator.php +++ b/src/LiveComponent/src/LiveComponentHydrator.php @@ -370,6 +370,7 @@ private function adjustPropertyPathForData(mixed $rawPropertyValue, string $prop foreach ($parts as $part) { if (\is_array($currentValue)) { $finalPropertyPath .= \sprintf('[%s]', $part); + $currentValue = $this->propertyAccessor->getValue($rawPropertyValue, $finalPropertyPath); continue; } @@ -379,6 +380,10 @@ private function adjustPropertyPathForData(mixed $rawPropertyValue, string $prop } $finalPropertyPath .= $part; + + if (null !== $currentValue) { + $currentValue = $this->propertyAccessor->getValue($rawPropertyValue, $finalPropertyPath); + } } return $finalPropertyPath; diff --git a/src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php b/src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php new file mode 100644 index 00000000000..13eaec51f94 --- /dev/null +++ b/src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php @@ -0,0 +1,13 @@ + [function () { + $address1 = create(Address::class, ['address' => '17 Arcadia Road', 'city' => 'London'])->object(); + $address2 = create(Address::class, ['address' => '4 Privet Drive', 'city' => 'Little Whinging'])->object(); + $address3 = create(Address::class, ['address' => '124 Conch St.', 'city' => 'Bikini Bottom'])->object(); + $address4 = create(Address::class, ['address' => '32 Windsor Gardens', 'city' => 'London'])->object(); + + return HydrationTest::create(new class() { + /** + * @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address[] + */ + #[LiveProp(writable: true, useSerializerForHydration: true)] + public array $addresses = []; + }) + ->mountWith(['addresses' => [$address1, $address2]]) + ->assertDehydratesTo(['addresses' => [ + [ + 'address' => '17 Arcadia Road', + 'city' => 'London', + ], + [ + 'address' => '4 Privet Drive', + 'city' => 'Little Whinging', + ], + ]]) + ->userUpdatesProps(['addresses' => [$address3, $address4]]) + ->assertObjectAfterHydration(function (object $object) { + self::assertEquals([ + create(Address::class, ['address' => '124 Conch St.', 'city' => 'Bikini Bottom'])->object(), + create(Address::class, ['address' => '32 Windsor Gardens', 'city' => 'London'])->object(), + ], $object->addresses); + }); + }]; + + yield 'Array with DTOs: fully writable allows partial changes' => [function () { + $address1 = create(Address::class, ['address' => '1600 Pennsylvania Avenue', 'city' => 'Washington DC'])->object(); + $address2 = create(Address::class, ['address' => '221 B Baker St', 'city' => 'Birmingham'])->object(); + + return HydrationTest::create(new class() { + /** + * @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address[] + */ + #[LiveProp(writable: true, useSerializerForHydration: true)] + public array $addresses = []; + }) + ->mountWith(['addresses' => [$address1, $address2]]) + ->assertDehydratesTo(['addresses' => [ + [ + 'address' => '1600 Pennsylvania Avenue', + 'city' => 'Washington DC', + ], + [ + 'address' => '221 B Baker St', + 'city' => 'Birmingham', + ], + ]]) + ->userUpdatesProps(['addresses.1.city' => 'London']) + ->assertObjectAfterHydration(function (object $object) { + self::assertEquals([ + create(Address::class, ['address' => '1600 Pennsylvania Avenue', 'city' => 'Washington DC'])->object(), + create(Address::class, ['address' => '221 B Baker St', 'city' => 'London'])->object(), + ], $object->addresses); + }); + }]; + + yield 'Array with DTOs: fully writable allows deep partial changes' => [function () { + return HydrationTest::create(new class() { + /** + * @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\HoldsArrayOfDtos[] + */ + #[LiveProp(writable: true, useSerializerForHydration: true)] + public array $dtos = []; + }) + ->mountWith(['dtos' => [ + create(HoldsArrayOfDtos::class, ['addresses' => [ + create(Address::class, ['address' => '742 Evergreen Terrace', 'city' => 'Boston'])->object(), + create(Address::class, ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'])->object(), + create(Address::class, ['address' => '52 Festive Road', 'city' => 'London'])->object(), + ]])->object(), + create(HoldsArrayOfDtos::class, ['addresses' => [ + create(Address::class, ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'])->object(), + create(Address::class, ['address' => 'Madison Square Garden', 'city' => 'Chicago'])->object(), + ]])->object(), + ]]) + ->assertDehydratesTo(['dtos' => [ + [ + 'addresses' => [ + ['address' => '742 Evergreen Terrace', 'city' => 'Boston'], + ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'], + ['address' => '52 Festive Road', 'city' => 'London'], + ], + ], + [ + 'addresses' => [ + ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'], + ['address' => 'Madison Square Garden', 'city' => 'Chicago'], + ], + ], + ]]) + ->userUpdatesProps([ + 'dtos.0.addresses.0.city' => 'Springfield', + 'dtos.1.addresses.1.address' => '1060 West Addison Street', + 'dtos.1.addresses.1' => create(Address::class, ['address' => '10 Downing Street', 'city' => 'London'])->object(), + ]) + ->assertObjectAfterHydration(function (object $object) { + self::assertEquals( + [ + create(HoldsArrayOfDtos::class, ['addresses' => [ + create(Address::class, ['address' => '742 Evergreen Terrace', 'city' => 'Springfield'])->object(), + create(Address::class, ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'])->object(), + create(Address::class, ['address' => '52 Festive Road', 'city' => 'London'])->object(), + ]])->object(), + create(HoldsArrayOfDtos::class, ['addresses' => [ + create(Address::class, ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'])->object(), + create(Address::class, ['address' => '10 Downing Street', 'city' => 'London'])->object(), + ]])->object(), + ], + $object->dtos + ); + }); + }]; + yield 'Object: (de)hydrates nested objects with phpdoc typehints' => [function () { return HydrationTest::create(new class() { #[LiveProp(writable: true)]