Skip to content

Commit 66bcbca

Browse files
committed
bug #1990 [LiveComponent] Allow updates in arrays of DTOs (cgrabenstein)
This PR was merged into the 2.x branch. Discussion ---------- [LiveComponent] Allow updates in arrays of DTOs | Q | A | ------------- | --- | Bug fix? | yes | New feature? |no <!-- please update src/**/CHANGELOG.md files --> | Issues | Fix #1953 <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead --> | License | MIT This PRs allows changes in arrays of DTOs, see the linked issue for more background information. There is one limitation though: For now it is not possible to generally set nested properties to be writable (i.e. something like `writable: 'items.*.prop'`, or `items[].prop`). You either have to set everything to writable (`writable: true`), or a very specific element (`writable: items.1.prop`). Commits ------- 5650ecf Allow updates in arrays of DTOs
2 parents 33a95e9 + 5650ecf commit 66bcbca

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

src/LiveComponent/src/LiveComponentHydrator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ private function adjustPropertyPathForData(mixed $rawPropertyValue, string $prop
370370
foreach ($parts as $part) {
371371
if (\is_array($currentValue)) {
372372
$finalPropertyPath .= \sprintf('[%s]', $part);
373+
$currentValue = $this->propertyAccessor->getValue($rawPropertyValue, $finalPropertyPath);
373374

374375
continue;
375376
}
@@ -379,6 +380,10 @@ private function adjustPropertyPathForData(mixed $rawPropertyValue, string $prop
379380
}
380381

381382
$finalPropertyPath .= $part;
383+
384+
if (null !== $currentValue) {
385+
$currentValue = $this->propertyAccessor->getValue($rawPropertyValue, $finalPropertyPath);
386+
}
382387
}
383388

384389
return $finalPropertyPath;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Dto;
6+
7+
final class HoldsArrayOfDtos
8+
{
9+
/**
10+
* @var Address[] $addresses
11+
*/
12+
public array $addresses;
13+
}

src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\BlogPostWithSerializationContext;
2424
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\CustomerDetails;
2525
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Embeddable2;
26+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\HoldsArrayOfDtos;
2627
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Money;
2728
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\ParentDTO;
2829
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\SimpleDto;
@@ -968,6 +969,127 @@ public function mount()
968969
});
969970
}];
970971

972+
yield 'Array with DTOs: fully writable allows anything to change' => [function () {
973+
$address1 = create(Address::class, ['address' => '17 Arcadia Road', 'city' => 'London'])->object();
974+
$address2 = create(Address::class, ['address' => '4 Privet Drive', 'city' => 'Little Whinging'])->object();
975+
$address3 = create(Address::class, ['address' => '124 Conch St.', 'city' => 'Bikini Bottom'])->object();
976+
$address4 = create(Address::class, ['address' => '32 Windsor Gardens', 'city' => 'London'])->object();
977+
978+
return HydrationTest::create(new class() {
979+
/**
980+
* @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address[]
981+
*/
982+
#[LiveProp(writable: true, useSerializerForHydration: true)]
983+
public array $addresses = [];
984+
})
985+
->mountWith(['addresses' => [$address1, $address2]])
986+
->assertDehydratesTo(['addresses' => [
987+
[
988+
'address' => '17 Arcadia Road',
989+
'city' => 'London',
990+
],
991+
[
992+
'address' => '4 Privet Drive',
993+
'city' => 'Little Whinging',
994+
],
995+
]])
996+
->userUpdatesProps(['addresses' => [$address3, $address4]])
997+
->assertObjectAfterHydration(function (object $object) {
998+
self::assertEquals([
999+
create(Address::class, ['address' => '124 Conch St.', 'city' => 'Bikini Bottom'])->object(),
1000+
create(Address::class, ['address' => '32 Windsor Gardens', 'city' => 'London'])->object(),
1001+
], $object->addresses);
1002+
});
1003+
}];
1004+
1005+
yield 'Array with DTOs: fully writable allows partial changes' => [function () {
1006+
$address1 = create(Address::class, ['address' => '1600 Pennsylvania Avenue', 'city' => 'Washington DC'])->object();
1007+
$address2 = create(Address::class, ['address' => '221 B Baker St', 'city' => 'Birmingham'])->object();
1008+
1009+
return HydrationTest::create(new class() {
1010+
/**
1011+
* @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address[]
1012+
*/
1013+
#[LiveProp(writable: true, useSerializerForHydration: true)]
1014+
public array $addresses = [];
1015+
})
1016+
->mountWith(['addresses' => [$address1, $address2]])
1017+
->assertDehydratesTo(['addresses' => [
1018+
[
1019+
'address' => '1600 Pennsylvania Avenue',
1020+
'city' => 'Washington DC',
1021+
],
1022+
[
1023+
'address' => '221 B Baker St',
1024+
'city' => 'Birmingham',
1025+
],
1026+
]])
1027+
->userUpdatesProps(['addresses.1.city' => 'London'])
1028+
->assertObjectAfterHydration(function (object $object) {
1029+
self::assertEquals([
1030+
create(Address::class, ['address' => '1600 Pennsylvania Avenue', 'city' => 'Washington DC'])->object(),
1031+
create(Address::class, ['address' => '221 B Baker St', 'city' => 'London'])->object(),
1032+
], $object->addresses);
1033+
});
1034+
}];
1035+
1036+
yield 'Array with DTOs: fully writable allows deep partial changes' => [function () {
1037+
return HydrationTest::create(new class() {
1038+
/**
1039+
* @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\HoldsArrayOfDtos[]
1040+
*/
1041+
#[LiveProp(writable: true, useSerializerForHydration: true)]
1042+
public array $dtos = [];
1043+
})
1044+
->mountWith(['dtos' => [
1045+
create(HoldsArrayOfDtos::class, ['addresses' => [
1046+
create(Address::class, ['address' => '742 Evergreen Terrace', 'city' => 'Boston'])->object(),
1047+
create(Address::class, ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'])->object(),
1048+
create(Address::class, ['address' => '52 Festive Road', 'city' => 'London'])->object(),
1049+
]])->object(),
1050+
create(HoldsArrayOfDtos::class, ['addresses' => [
1051+
create(Address::class, ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'])->object(),
1052+
create(Address::class, ['address' => 'Madison Square Garden', 'city' => 'Chicago'])->object(),
1053+
]])->object(),
1054+
]])
1055+
->assertDehydratesTo(['dtos' => [
1056+
[
1057+
'addresses' => [
1058+
['address' => '742 Evergreen Terrace', 'city' => 'Boston'],
1059+
['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'],
1060+
['address' => '52 Festive Road', 'city' => 'London'],
1061+
],
1062+
],
1063+
[
1064+
'addresses' => [
1065+
['address' => '698 Sycamore Road', 'city' => 'San Pueblo'],
1066+
['address' => 'Madison Square Garden', 'city' => 'Chicago'],
1067+
],
1068+
],
1069+
]])
1070+
->userUpdatesProps([
1071+
'dtos.0.addresses.0.city' => 'Springfield',
1072+
'dtos.1.addresses.1.address' => '1060 West Addison Street',
1073+
'dtos.1.addresses.1' => create(Address::class, ['address' => '10 Downing Street', 'city' => 'London'])->object(),
1074+
])
1075+
->assertObjectAfterHydration(function (object $object) {
1076+
self::assertEquals(
1077+
[
1078+
create(HoldsArrayOfDtos::class, ['addresses' => [
1079+
create(Address::class, ['address' => '742 Evergreen Terrace', 'city' => 'Springfield'])->object(),
1080+
create(Address::class, ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'])->object(),
1081+
create(Address::class, ['address' => '52 Festive Road', 'city' => 'London'])->object(),
1082+
]])->object(),
1083+
create(HoldsArrayOfDtos::class, ['addresses' => [
1084+
create(Address::class, ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'])->object(),
1085+
create(Address::class, ['address' => '10 Downing Street', 'city' => 'London'])->object(),
1086+
]])->object(),
1087+
],
1088+
$object->dtos
1089+
);
1090+
});
1091+
}];
1092+
9711093
yield 'Object: (de)hydrates nested objects with phpdoc typehints' => [function () {
9721094
return HydrationTest::create(new class() {
9731095
#[LiveProp(writable: true)]

0 commit comments

Comments
 (0)