Skip to content

Commit d14d4fd

Browse files
committed
[Live] lower the normalizer priority of DoctrineObjectNormalizer
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.
1 parent 85760ce commit d14d4fd

File tree

10 files changed

+267
-1
lines changed

10 files changed

+267
-1
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
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Money;
9+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Temperature;
10+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Embeddable;
11+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
12+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity2;
13+
14+
#[AsLiveComponent('with_objects')]
15+
final class WithObjects
16+
{
17+
use DefaultActionTrait;
18+
19+
#[LiveProp]
20+
public Money $money;
21+
22+
#[LiveProp]
23+
public Temperature $temperature;
24+
25+
#[LiveProp]
26+
public Entity1 $entity1;
27+
28+
#[LiveProp]
29+
public Entity2 $entity2;
30+
31+
#[LiveProp]
32+
public Embeddable $embeddable;
33+
}
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 Embeddable
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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 Entity2
20+
{
21+
/**
22+
* @ORM\Id
23+
* @ORM\GeneratedValue
24+
* @ORM\Column(type="integer")
25+
*/
26+
public $id;
27+
28+
/**
29+
* @ORM\Embedded
30+
*/
31+
public Embeddable $embedded;
32+
}

src/LiveComponent/tests/Fixtures/Kernel.php

Lines changed: 4 additions & 0 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

@@ -90,6 +92,8 @@ protected function configureContainer(ContainerConfigurator $c): void
9092
->autoconfigure()
9193
// disable logging errors to the console
9294
->set('logger', NullLogger::class)
95+
->set(MoneyNormalizer::class)->autoconfigure()->autowire()
96+
->set(Entity2Normalizer::class)->autoconfigure()->autowire()
9397
->load(__NAMESPACE__.'\\Component\\', __DIR__.'/Component')
9498
;
9599
}
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+
}

src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2;
1717
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component3;
1818
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\ComponentWithArrayProp;
19+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Money;
20+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Temperature;
21+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Embeddable;
1922
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
23+
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity2;
2024
use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\EmptyStringEnum;
2125
use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\IntEnum;
2226
use Symfony\UX\LiveComponent\Tests\Fixtures\Enum\StringEnum;
@@ -339,4 +343,35 @@ public function testCanHydrateEnums(): void
339343

340344
$this->assertSame(ZeroIntEnum::ZERO, $mounted->getComponent()->zeroInt);
341345
}
346+
347+
public function testComponentWithNormalizableObjects(): void
348+
{
349+
$mounted = $this->mountComponent('with_objects', [
350+
'money' => new Money(500, 'CAD'),
351+
'temperature' => new Temperature(30, 'C'),
352+
'entity1' => $entity1 = create(Entity1::class)->object(),
353+
'entity2' => $entity2 = create(Entity2::class, ['embedded' => new Embeddable('bar')])->object(),
354+
'embeddable' => new Embeddable('foo'),
355+
]);
356+
357+
$dehydrated = $this->dehydrateComponent($mounted);
358+
359+
$this->assertSame('500|CAD', $dehydrated['money']);
360+
$this->assertSame(['degrees' => 30, 'uom' => 'C'], $dehydrated['temperature']);
361+
$this->assertSame($entity1->id, $dehydrated['entity1']);
362+
$this->assertSame('entity2:'.$entity2->id, $dehydrated['entity2']);
363+
$this->assertSame(['name' => 'foo'], $dehydrated['embeddable']);
364+
365+
$mounted = $this->hydrateComponent($this->getComponent('with_objects'), $dehydrated, $mounted->getName());
366+
$component = $mounted->getComponent();
367+
368+
$this->assertSame(500, $component->money->amount);
369+
$this->assertSame('CAD', $component->money->currency);
370+
$this->assertSame(30, $component->temperature->degrees);
371+
$this->assertSame('C', $component->temperature->uom);
372+
$this->assertSame($entity1->id, $component->entity1->id);
373+
$this->assertSame($entity2->id, $component->entity2->id);
374+
$this->assertSame('bar', $component->entity2->embedded->name);
375+
$this->assertSame('foo', $component->embeddable->name);
376+
}
342377
}

0 commit comments

Comments
 (0)