Skip to content

Commit b2441fa

Browse files
committed
bug #453 [Live] Lower the normalizer priority of DoctrineObjectNormalizer and fix embeddables (kbond)
This PR was merged into the 2.x branch. Discussion ---------- [Live] Lower the normalizer priority of `DoctrineObjectNormalizer` and fix embeddables | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Tickets | Fix #452 | License | MIT Changed from 100 to -100, we want a user's entity normalizers to run before. We also want `DoctrineObjectNormalizer` to run before the default object normalizer. Tests added to cover the following ways object properties on a component are normalized: - entity with no custom normalizer (uses `DoctrineObjectNormalizer`) - entity with custom normalizer - dto with custom normalizer - dto with no custom normalizer (uses the default object normalizer) - entity with embeddable (mapped using annotations) - entity with embeddable (mapped using xml) - dto with embeddable (mapped using annotations) - dto with embeddable (mapped using xml) Commits ------- d295fa8 [Live] lower the normalizer priority of `DoctrineObjectNormalizer`
2 parents bc72ec7 + d295fa8 commit b2441fa

File tree

13 files changed

+327
-4
lines changed

13 files changed

+327
-4
lines changed

src/LiveComponent/src/DependencyInjection/Compiler/OptionalDependencyPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function process(ContainerBuilder $container): void
2222
if ($container->hasDefinition('doctrine')) {
2323
$container->register('ux.live_component.doctrine_object_normalizer', DoctrineObjectNormalizer::class)
2424
->setArguments([new IteratorArgument([new Reference('doctrine')])]) // todo add other object managers (mongo)
25-
->addTag('serializer.normalizer', ['priority' => 100])
25+
->addTag('serializer.normalizer', ['priority' => -100])
2626
;
2727
}
2828
}

src/LiveComponent/src/Normalizer/DoctrineObjectNormalizer.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\UX\LiveComponent\Normalizer;
1313

14+
use Doctrine\ORM\EntityManagerInterface;
1415
use Doctrine\Persistence\ManagerRegistry;
1516
use Doctrine\Persistence\ObjectManager;
1617
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
@@ -83,10 +84,29 @@ private function objectManagerFor(string $class): ?ObjectManager
8384
// todo cache/warmup an array of classes that are "doctrine objects"
8485
foreach ($this->managerRegistries as $registry) {
8586
if ($om = $registry->getManagerForClass($class)) {
86-
return $om;
87+
return self::ensureManagedObject($om, $class);
8788
}
8889
}
8990

9091
return null;
9192
}
93+
94+
/**
95+
* Ensure the $class is not embedded or a mapped superclass.
96+
*/
97+
private static function ensureManagedObject(ObjectManager $om, string $class): ?ObjectManager
98+
{
99+
if (!$om instanceof EntityManagerInterface) {
100+
// todo might need to add some checks once ODM support is added
101+
return $om;
102+
}
103+
104+
$metadata = $om->getClassMetadata($class);
105+
106+
if ($metadata->isEmbeddedClass || $metadata->isMappedSuperclass) {
107+
return null;
108+
}
109+
110+
return $om;
111+
}
92112
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
4+
5+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
6+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
7+
use Symfony\UX\LiveComponent\DefaultActionTrait;
8+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Embeddable2;
9+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Money;
10+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Temperature;
11+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Embeddable1;
12+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
13+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity2;
14+
15+
#[AsLiveComponent('with_objects')]
16+
final class WithObjects
17+
{
18+
use DefaultActionTrait;
19+
20+
#[LiveProp]
21+
public Money $money;
22+
23+
#[LiveProp]
24+
public Temperature $temperature;
25+
26+
#[LiveProp]
27+
public Entity1 $entity1;
28+
29+
#[LiveProp]
30+
public Entity2 $entity2;
31+
32+
#[LiveProp]
33+
public Embeddable1 $embeddable1;
34+
35+
#[LiveProp]
36+
public Embeddable2 $embeddable2;
37+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Dto;
4+
5+
final class Embeddable2
6+
{
7+
public function __construct(public string $name)
8+
{
9+
}
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Dto;
13+
14+
/**
15+
* @author Kevin Bond <[email protected]>
16+
*/
17+
final class Money
18+
{
19+
public function __construct(public int $amount, public string $currency)
20+
{
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Dto;
13+
14+
/**
15+
* @author Kevin Bond <[email protected]>
16+
*/
17+
final class Temperature
18+
{
19+
public function __construct(public int $degrees, public string $uom)
20+
{
21+
}
22+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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\Embeddable
18+
*/
19+
final class Embeddable1
20+
{
21+
/**
22+
* @ORM\Column
23+
*/
24+
public string $name;
25+
26+
public function __construct(string $name)
27+
{
28+
$this->name = $name;
29+
}
30+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\UX\LiveComponent\Tests\Fixtures\Dto\Embeddable2;
16+
17+
/**
18+
* @ORM\Entity
19+
*/
20+
class Entity2
21+
{
22+
/**
23+
* @ORM\Id
24+
* @ORM\GeneratedValue
25+
* @ORM\Column(type="integer")
26+
*/
27+
public $id;
28+
29+
/**
30+
* @ORM\Embedded(Embeddable1::class)
31+
*/
32+
public Embeddable1 $embedded1;
33+
34+
/**
35+
* @ORM\Embedded(Embeddable2::class)
36+
*/
37+
public Embeddable2 $embedded2;
38+
}

src/LiveComponent/tests/Fixtures/Kernel.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
2222
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
2323
use Symfony\UX\LiveComponent\LiveComponentBundle;
24+
use Symfony\UX\LiveComponent\Tests\Fixtures\Serializer\Entity2Normalizer;
25+
use Symfony\UX\LiveComponent\Tests\Fixtures\Serializer\MoneyNormalizer;
2426
use Symfony\UX\TwigComponent\TwigComponentBundle;
2527
use Twig\Environment;
2628

@@ -73,12 +75,19 @@ protected function configureContainer(ContainerConfigurator $c): void
7375
'auto_generate_proxy_classes' => true,
7476
'auto_mapping' => true,
7577
'mappings' => [
76-
'Test' => [
78+
'Default' => [
7779
'is_bundle' => false,
7880
'type' => 'annotation',
7981
'dir' => '%kernel.project_dir%/tests/Fixtures/Entity',
8082
'prefix' => 'Symfony\UX\LiveComponent\Tests\Fixtures\Entity',
81-
'alias' => 'Test',
83+
'alias' => 'Default',
84+
],
85+
'XML' => [
86+
'is_bundle' => false,
87+
'type' => 'xml',
88+
'dir' => '%kernel.project_dir%/tests/Fixtures/config/doctrine',
89+
'prefix' => 'Symfony\UX\LiveComponent\Tests\Fixtures\Dto',
90+
'alias' => 'XML',
8291
],
8392
],
8493
],
@@ -90,6 +99,8 @@ protected function configureContainer(ContainerConfigurator $c): void
9099
->autoconfigure()
91100
// disable logging errors to the console
92101
->set('logger', NullLogger::class)
102+
->set(MoneyNormalizer::class)->autoconfigure()->autowire()
103+
->set(Entity2Normalizer::class)->autoconfigure()->autowire()
93104
->load(__NAMESPACE__.'\\Component\\', __DIR__.'/Component')
94105
;
95106
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\Serializer;
13+
14+
use Doctrine\Persistence\ManagerRegistry;
15+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
16+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
17+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity2;
18+
19+
/**
20+
* @author Kevin Bond <[email protected]>
21+
*/
22+
final class Entity2Normalizer implements NormalizerInterface, DenormalizerInterface
23+
{
24+
public function __construct(private ManagerRegistry $doctrine)
25+
{
26+
}
27+
28+
public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
29+
{
30+
[, $id] = \explode(':', $data);
31+
32+
return $this->doctrine->getRepository(Entity2::class)->find($id);
33+
}
34+
35+
public function supportsDenormalization(mixed $data, string $type, string $format = null)
36+
{
37+
return Entity2::class === $type;
38+
}
39+
40+
public function normalize(mixed $object, string $format = null, array $context = [])
41+
{
42+
return 'entity2:'.$object->id;
43+
}
44+
45+
public function supportsNormalization(mixed $data, string $format = null)
46+
{
47+
return $data instanceof Entity2;
48+
}
49+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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\Serializer;
13+
14+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
15+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
16+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Money;
17+
18+
final class MoneyNormalizer implements NormalizerInterface, DenormalizerInterface
19+
{
20+
public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
21+
{
22+
return new Money(...\explode('|', $data));
23+
}
24+
25+
public function supportsDenormalization(mixed $data, string $type, string $format = null)
26+
{
27+
return Money::class === $type;
28+
}
29+
30+
public function normalize(mixed $object, string $format = null, array $context = [])
31+
{
32+
return \implode('|', [$object->amount, $object->currency]);
33+
}
34+
35+
public function supportsNormalization(mixed $data, string $format = null)
36+
{
37+
return $data instanceof Money;
38+
}
39+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<doctrine-mapping>
2+
<embeddable name="Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Embeddable2">
3+
<field name="name" type="string" />
4+
</embeddable>
5+
</doctrine-mapping>

0 commit comments

Comments
 (0)