Skip to content

Commit 1cd97fd

Browse files
committed
[Live] Ignore problems with writing bad types for writable paths
The motivating use-case is where you have a writable property that starts null but the setter method does NOT allow null. When that null is rehydrated later, we attempt to call the setter with a null value, which explodes. This makes for a better UX... though with a sprinkle of magic to make it happen.
1 parent 8eb4e08 commit 1cd97fd

File tree

3 files changed

+73
-12
lines changed

3 files changed

+73
-12
lines changed

src/LiveComponent/src/LiveComponentHydrator.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ public function hydrate(object $component, array $props, array $updatedProps, Li
159159
);
160160

161161
/*
162-
| 2) Set UPDATED "writable paths" data for this LiveProp.
162+
| 2) Set ORIGINAL "writable paths" data for this LiveProp.
163+
| (which may represent data that was updated on a previous request)
163164
*/
164165
$originalWritablePaths = $this->calculateWritablePaths($propMetadata, $propertyValue, $dehydratedOriginalProps, $frontendName, \get_class($component));
165166
$propertyValue = $this->setWritablePaths(
@@ -194,16 +195,12 @@ public function hydrate(object $component, array $props, array $updatedProps, Li
194195
| 4) Set UPDATED "writable paths" data for this LiveProp.
195196
*/
196197
$updatedWritablePaths = $this->calculateWritablePaths($propMetadata, $propertyValue, $dehydratedUpdatedProps, $frontendName, \get_class($component));
197-
try {
198-
$propertyValue = $this->setWritablePaths(
199-
$updatedWritablePaths,
200-
$frontendName,
201-
$propertyValue,
202-
$dehydratedUpdatedProps,
203-
);
204-
} catch (HydrationException $e) {
205-
// swallow this: it's bad data from the user
206-
}
198+
$propertyValue = $this->setWritablePaths(
199+
$updatedWritablePaths,
200+
$frontendName,
201+
$propertyValue,
202+
$dehydratedUpdatedProps,
203+
);
207204

208205
try {
209206
$this->propertyAccessor->setValue($component, $propMetadata->getName(), $propertyValue);
@@ -316,7 +313,10 @@ private function setWritablePaths(array $writablePaths, string $frontendPropName
316313
$writablePathData
317314
);
318315
} catch (PropertyAccessExceptionInterface $exception) {
319-
throw new HydrationException(sprintf('The model "%s.%s" was sent for update, but it could not be set: %s', $frontendPropName, $writablePath, $exception->getMessage()), $exception);
316+
// The user likely sent bad data (e.g. string for a number field).
317+
// Simply fail to set the field and use the previous value
318+
// (this can also happen with original, writable data - e.g. if
319+
// a property is "null", but the setter method doesn't allow null).
320320
}
321321
}
322322

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Entity;
13+
14+
use Doctrine\ORM\Mapping as ORM;
15+
16+
/**
17+
* @ORM\Entity
18+
*/
19+
class CategoryFixtureEntity
20+
{
21+
/**
22+
* @ORM\Id
23+
* @ORM\GeneratedValue
24+
* @ORM\Column(type="integer")
25+
*/
26+
private ?int $id = null;
27+
28+
/**
29+
* @ORM\Column(type="string")
30+
*/
31+
private ?string $name = null;
32+
33+
public function getId(): ?int
34+
{
35+
return $this->id;
36+
}
37+
38+
public function getName(): ?string
39+
{
40+
return $this->name;
41+
}
42+
43+
public function setName(string $name)
44+
{
45+
$this->name = $name;
46+
}
47+
}

src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Embeddable2;
2424
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Money;
2525
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Temperature;
26+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\CategoryFixtureEntity;
2627
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Embeddable1;
2728
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
2829
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity2;
@@ -1066,6 +1067,19 @@ public function provideInvalidHydrationTests(): iterable
10661067
$this->assertSame(1, $object->nonNullableInt->value);
10671068
});
10681069
}, 80100];
1070+
1071+
yield 'writable_path_with_type_problem_ignored' => [function () {
1072+
return HydrationTest::create(new class() {
1073+
#[LiveProp(writable: ['name'])]
1074+
public CategoryFixtureEntity $category;
1075+
})
1076+
->mountWith(['category' => new CategoryFixtureEntity()])
1077+
->assertObjectAfterHydration(function (object $object) {
1078+
// dehydrating category.name=null does not cause issues
1079+
// even though setName() does not allow null
1080+
$this->assertNull($object->category->getName());
1081+
});
1082+
}];
10691083
}
10701084

10711085
public function testHydrationFailsIfChecksumMissing(): void

0 commit comments

Comments
 (0)