Skip to content

Commit a5d5d20

Browse files
committed
[LiveComponent] hydrateWith is not used when submitting nested data
1 parent 9bc8da7 commit a5d5d20

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

src/LiveComponent/src/LiveComponentHydrator.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@ public function hydrate(object $component, array $props, array $updatedProps, Li
222222
$dehydratedUpdatedProps,
223223
);
224224

225+
if (0 < \count($updatedWritablePaths)) {
226+
try {
227+
$propertyValue = $this->hydrateValue(
228+
$propertyValue,
229+
$propMetadata,
230+
$component,
231+
);
232+
} catch (HydrationException $e) {
233+
// swallow this: it's bad data from the user
234+
}
235+
}
236+
225237
try {
226238
$this->propertyAccessor->setValue($component, $propMetadata->getName(), $propertyValue);
227239
} catch (PropertyAccessExceptionInterface $exception) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Symfony package.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
15+
16+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
17+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
18+
use Symfony\UX\LiveComponent\DefaultActionTrait;
19+
20+
#[AsLiveComponent]
21+
final class ComponentWithHydrateWithProps
22+
{
23+
use DefaultActionTrait;
24+
25+
/**
26+
* @var array<string, int>
27+
*/
28+
#[LiveProp(writable: true, hydrateWith: 'hydrateIntegers')]
29+
public array $integers = [
30+
'one' => 1,
31+
'two' => 2,
32+
'three' => 3,
33+
];
34+
35+
/**
36+
* @param array<string, mixed> $data
37+
*
38+
* @return array<string, int>
39+
*/
40+
public function hydrateIntegers(array $data): array
41+
{
42+
return array_map(intval(...), $data);
43+
}
44+
}

src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\UX\LiveComponent\Tests\Unit;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\PropertyAccess\PropertyAccess;
1516
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1617
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
1718
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
@@ -20,9 +21,13 @@
2021
use Symfony\UX\LiveComponent\Attribute\LiveProp;
2122
use Symfony\UX\LiveComponent\LiveComponentHydrator;
2223
use Symfony\UX\LiveComponent\Metadata\LegacyLivePropMetadata;
24+
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadata;
2325
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
2426
use Symfony\UX\LiveComponent\Metadata\LivePropMetadata;
27+
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\ComponentWithHydrateWithProps;
28+
use Symfony\UX\TwigComponent\ComponentMetadata;
2529
use Twig\Environment;
30+
use Twig\Runtime\EscaperRuntime;
2631

2732
final class LiveComponentHydratorTest extends TestCase
2833
{
@@ -80,6 +85,76 @@ public function testItCanHydrateWithNullValues()
8085
self::assertNull($hydratedValue);
8186
}
8287
}
88+
89+
public function testHydrateWithIsCalledWithNestedArrayAsProps()
90+
{
91+
$twigMock = $this->createMock(Environment::class);
92+
$twigMock->method('getRuntime')->willReturn(new EscaperRuntime());
93+
$hydrator = new LiveComponentHydrator(
94+
[],
95+
PropertyAccess::createPropertyAccessor(),
96+
$this->createMock(LiveComponentMetadataFactory::class),
97+
new Serializer(normalizers: [new ObjectNormalizer()]),
98+
'foo',
99+
$twigMock,
100+
);
101+
$component = new ComponentWithHydrateWithProps();
102+
103+
// BC layer when "symfony/type-info" is not available
104+
if (!class_exists(Type::class)) {
105+
$hydrator->hydrate(
106+
$component,
107+
[
108+
'@checksum' => 'SNKH1tHUgwgWT8V+Z/A7z126JQ2dWGiG6xVmjx7FbeA=',
109+
'integers' => [
110+
'one' => 1,
111+
'two' => 2,
112+
'three' => 3,
113+
],
114+
],
115+
[
116+
'integers.one' => '4',
117+
'integers.two' => '5',
118+
'integers.three' => '6',
119+
],
120+
new LiveComponentMetadata(
121+
new ComponentMetadata([]),
122+
[
123+
new LegacyLivePropMetadata('integers', new LiveProp(writable: true, hydrateWith: 'hydrateIntegers'), typeName: 'array', isBuiltIn: true, allowsNull: false, collectionValueType: new \Symfony\Component\PropertyInfo\Type('int')),
124+
],
125+
),
126+
);
127+
} else {
128+
$hydrator->hydrate(
129+
$component,
130+
[
131+
'@checksum' => 'SNKH1tHUgwgWT8V+Z/A7z126JQ2dWGiG6xVmjx7FbeA=',
132+
'integers' => [
133+
'one' => 1,
134+
'two' => 2,
135+
'three' => 3,
136+
],
137+
],
138+
[
139+
'integers.one' => '4',
140+
'integers.two' => '5',
141+
'integers.three' => '6',
142+
],
143+
new LiveComponentMetadata(
144+
new ComponentMetadata([]),
145+
[
146+
new LivePropMetadata('integers', new LiveProp(writable: true, hydrateWith: 'hydrateIntegers'), Type::array(Type::int(), Type::string())),
147+
],
148+
),
149+
);
150+
}
151+
152+
self::assertSame([
153+
'one' => 4,
154+
'two' => 5,
155+
'three' => 6,
156+
], $component->integers);
157+
}
83158
}
84159

85160
class Foo

0 commit comments

Comments
 (0)