Skip to content

Commit d071359

Browse files
committed
[LiveComponent] Fix BC break when dealing with entities (implementing an interface) on LiveProp
1 parent bc84475 commit d071359

File tree

6 files changed

+191
-0
lines changed

6 files changed

+191
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\Component;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
15+
use Symfony\Component\Form\FormInterface;
16+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
17+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
18+
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
19+
use Symfony\UX\LiveComponent\DefaultActionTrait;
20+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\User;
21+
use Symfony\UX\LiveComponent\Tests\Fixtures\Form\UserFormType;
22+
23+
#[AsLiveComponent('form_with_user_interface', template: 'components/form_with_user_interface.html.twig')]
24+
class FormWithUserInterfaceComponent extends AbstractController
25+
{
26+
use ComponentWithFormTrait;
27+
use DefaultActionTrait;
28+
29+
#[LiveProp]
30+
public User $user;
31+
32+
protected function instantiateForm(): FormInterface
33+
{
34+
return $this->createForm(UserFormType::class, $this->user);
35+
}
36+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
use Symfony\Component\Security\Core\User\UserInterface;
16+
17+
#[ORM\Entity]
18+
class User implements UserInterface
19+
{
20+
#[ORM\Id]
21+
#[ORM\GeneratedValue]
22+
#[ORM\Column(type: 'integer')]
23+
public $id;
24+
25+
#[ORM\Column(type: 'string', length: 180, unique: true)]
26+
public $username;
27+
28+
public function getRoles(): array
29+
{
30+
return ['ROLE_USER'];
31+
}
32+
33+
public function eraseCredentials(): void
34+
{
35+
// no-op
36+
}
37+
38+
public function getUsername(): string
39+
{
40+
return $this->getUserIdentifier();
41+
}
42+
43+
public function getUserIdentifier(): string
44+
{
45+
return $this->username;
46+
}
47+
48+
public function getPassword(): ?string
49+
{
50+
return null;
51+
}
52+
53+
public function getSalt(): ?string
54+
{
55+
return null;
56+
}
57+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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\Form;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
use Symfony\Component\Form\Extension\Core\Type\TextType;
16+
use Symfony\Component\Form\FormBuilderInterface;
17+
use Symfony\Component\OptionsResolver\OptionsResolver;
18+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\User;
19+
20+
class UserFormType extends AbstractType
21+
{
22+
public function buildForm(FormBuilderInterface $builder, array $options)
23+
{
24+
$builder
25+
->add('username', TextType::class)
26+
;
27+
}
28+
29+
public function configureOptions(OptionsResolver $resolver)
30+
{
31+
$resolver->setDefaults([
32+
'data_class' => User::class,
33+
]);
34+
}
35+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div{{ attributes }}>
2+
{{ form(form) }}
3+
</div>

src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
use Symfony\Component\DomCrawler\Crawler;
1616
use Symfony\Component\Form\FormFactoryInterface;
1717
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\FormWithCollectionTypeComponent;
18+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\User;
1819
use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
1920
use Symfony\UX\LiveComponent\Tests\Fixtures\Form\BlogPostFormType;
2021
use Symfony\UX\LiveComponent\Tests\LiveComponentTestHelper;
2122
use Zenstruck\Browser\Test\HasBrowser;
2223
use Zenstruck\Foundry\Test\Factories;
2324
use Zenstruck\Foundry\Test\ResetDatabase;
2425

26+
use function Zenstruck\Foundry\Persistence\persist;
27+
use function Zenstruck\Foundry\Persistence\refresh;
28+
2529
/**
2630
* @author Jakub Caban <[email protected]>
2731
*/
@@ -450,4 +454,38 @@ public function testDataModelAttributeAutomaticallyAdded(): void
450454
->assertElementAttributeContains('form', 'data-model', 'on(change)|*')
451455
;
452456
}
457+
458+
public function testFormWithLivePropContainingAnEntityImplementingAnInterface(): void
459+
{
460+
$user = persist(User::class, ['username' => 'Fabien']);
461+
self::assertInstanceOf(User::class, $user);
462+
self::assertEquals(1, $user->id);
463+
self::assertEquals('Fabien', $user->username);
464+
465+
$mounted = $this->mountComponent('form_with_user_interface', [
466+
'user' => $user,
467+
]);
468+
469+
$dehydrated = $this->dehydrateComponent($mounted)->getProps();
470+
471+
$this->browser()
472+
->post('/_components/form_with_user_interface', [
473+
'body' => [
474+
'data' => json_encode([
475+
'props' => $dehydrated,
476+
'updated' => [
477+
'user_form.username' => 'Nicolas',
478+
'validatedFields' => ['user_form.username'],
479+
],
480+
]),
481+
],
482+
])
483+
->assertStatus(200)
484+
->assertElementAttributeContains('form', 'data-model', 'on(change)|*')
485+
;
486+
487+
refresh($user);
488+
self::assertEquals(1, $user->id);
489+
self::assertEquals('Nicolas', $user->username);
490+
}
453491
}

src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
3838
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity2;
3939
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\ProductFixtureEntity;
40+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\User;
4041
use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\EmptyStringEnum;
4142
use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\IntEnum;
4243
use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\StringEnum;
@@ -336,6 +337,27 @@ public function onEntireEntityUpdated($oldValue)
336337
;
337338
}];
338339

340+
yield 'Persisted entity: (de)hydration works correctly to/from id, when the entity implements an interface' => [function () {
341+
$user = persist(User::class, [
342+
'username' => 'Fabien',
343+
]);
344+
\assert($user instanceof User);
345+
346+
return HydrationTest::create(new class {
347+
#[LiveProp]
348+
public User $user;
349+
})
350+
->mountWith(['user' => $user])
351+
->assertDehydratesTo(['user' => $user->id])
352+
->assertObjectAfterHydration(function (object $object) use ($user) {
353+
self::assertSame(
354+
$user->id,
355+
$object->user->id
356+
);
357+
})
358+
;
359+
}];
360+
339361
yield 'Persisted entity: writable CAN be changed via id' => [function () {
340362
$entityOriginal = persist(Entity1::class);
341363
$entityNext = persist(Entity1::class);

0 commit comments

Comments
 (0)