Skip to content

Commit 1a98973

Browse files
committed
Fix composite keys for broadcast
1 parent 67d29a5 commit 1a98973

File tree

8 files changed

+121
-42
lines changed

8 files changed

+121
-42
lines changed

src/Turbo/config/services.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
15+
use Symfony\UX\Turbo\Broadcaster\DoctrineIdAccessor;
1516
use Symfony\UX\Turbo\Broadcaster\IdAccessor;
1617
use Symfony\UX\Turbo\Broadcaster\ImuxBroadcaster;
1718
use Symfony\UX\Turbo\Broadcaster\TwigBroadcaster;
@@ -29,10 +30,17 @@
2930

3031
->alias(BroadcasterInterface::class, 'turbo.broadcaster.imux')
3132

33+
->set('turbo.id_formatter', IdAccessor::class)
34+
35+
->set('turbo.doctrine_id_accessor', DoctrineIdAccessor::class)
36+
->args([
37+
service('doctrine')->nullOnInvalid(),
38+
])
39+
3240
->set('turbo.id_accessor', IdAccessor::class)
3341
->args([
3442
service('property_accessor')->nullOnInvalid(),
35-
service('doctrine')->nullOnInvalid(),
43+
service('turbo.doctrine_id_accessor'),
3644
])
3745

3846
->set('turbo.broadcaster.action_renderer', TwigBroadcaster::class)
@@ -52,6 +60,7 @@
5260
->args([
5361
service('turbo.broadcaster.imux'),
5462
service('annotation_reader')->nullOnInvalid(),
63+
service('turbo.doctrine_id_accessor'),
5564
])
5665
->tag('doctrine.event_listener', ['event' => 'onFlush'])
5766
->tag('doctrine.event_listener', ['event' => 'postFlush'])

src/Turbo/src/Bridge/Mercure/Broadcaster.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Component\Mercure\HubInterface;
1616
use Symfony\Component\Mercure\Update;
1717
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
18+
use Symfony\UX\Turbo\Broadcaster\IdAccessor;
19+
use Symfony\UX\Turbo\Broadcaster\IdFormatter;
1820
use Symfony\UX\Turbo\Doctrine\ClassUtil;
1921

2022
/**
@@ -42,14 +44,16 @@ final class Broadcaster implements BroadcasterInterface
4244

4345
private $name;
4446
private $hub;
47+
private $idFormatter;
4548

4649
/** @var ExpressionLanguage|null */
4750
private $expressionLanguage;
4851

49-
public function __construct(string $name, HubInterface $hub)
52+
public function __construct(string $name, HubInterface $hub, ?IdFormatter $idFormatter = null)
5053
{
5154
$this->name = $name;
5255
$this->hub = $hub;
56+
$this->idFormatter = $idFormatter ?? new IdFormatter();
5357

5458
if (class_exists(ExpressionLanguage::class)) {
5559
$this->expressionLanguage = new ExpressionLanguage();
@@ -99,7 +103,7 @@ public function broadcast(object $entity, string $action, array $options): void
99103
throw new \InvalidArgumentException(sprintf('Cannot broadcast entity of class "%s": the option "topics" is empty and "id" is missing.', $entityClass));
100104
}
101105

102-
$id = $options['id_formatted'] ?? implode('-', (array) $options['id']);
106+
$id = $this->idFormatter->format($options['id']);
103107

104108
$options['topics'] = (array) sprintf(self::TOPIC_PATTERN, rawurlencode($entityClass), rawurlencode($id));
105109
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\Turbo\Broadcaster;
13+
14+
use Doctrine\Persistence\ManagerRegistry;
15+
use Doctrine\Persistence\ObjectManager;
16+
use Symfony\UX\Turbo\Doctrine\ClassUtil;
17+
18+
/**
19+
* @author Jason Schilling <[email protected]>
20+
*/
21+
class DoctrineIdAccessor
22+
{
23+
private $doctrine;
24+
25+
public function __construct(?ManagerRegistry $doctrine = null)
26+
{
27+
$this->doctrine = $doctrine;
28+
}
29+
30+
public function getEntityId(object $entity): ?array
31+
{
32+
$entityClass = $entity::class;
33+
34+
if ($this->doctrine && $em = $this->doctrine->getManagerForClass($entityClass)) {
35+
return $this->getIdentifierValues($em,$entity);
36+
}
37+
38+
return null;
39+
}
40+
41+
private function getIdentifierValues(ObjectManager $em, object $entity): array
42+
{
43+
$class = ClassUtil::getEntityClass($entity);
44+
45+
$values = $em->getClassMetadata($class)->getIdentifierValues($entity);
46+
47+
foreach ($values as $key => $value) {
48+
if (\is_object($value)) {
49+
$values[$key] = $this->getIdentifierValues($em, $value);
50+
}
51+
}
52+
53+
return $values;
54+
}
55+
}

src/Turbo/src/Broadcaster/IdAccessor.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@
1111

1212
namespace Symfony\UX\Turbo\Broadcaster;
1313

14-
use Doctrine\Persistence\ManagerRegistry;
1514
use Symfony\Component\PropertyAccess\PropertyAccess;
1615
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1716

1817
class IdAccessor
1918
{
2019
private $propertyAccessor;
21-
private $doctrine;
20+
private $doctrineIdAccessor;
2221

23-
public function __construct(?PropertyAccessorInterface $propertyAccessor = null, ?ManagerRegistry $doctrine = null)
22+
public function __construct(?PropertyAccessorInterface $propertyAccessor = null, ?DoctrineIdAccessor $doctrineIdAccessor = null)
2423
{
2524
$this->propertyAccessor = $propertyAccessor ?? (class_exists(PropertyAccess::class) ? PropertyAccess::createPropertyAccessor() : null);
26-
$this->doctrine = $doctrine;
25+
$this->doctrineIdAccessor = $doctrineIdAccessor ?? new DoctrineIdAccessor();
2726
}
2827

2928
/**
@@ -33,9 +32,8 @@ public function getEntityId(object $entity): ?array
3332
{
3433
$entityClass = $entity::class;
3534

36-
if ($this->doctrine && $em = $this->doctrine->getManagerForClass($entityClass)) {
37-
// @todo: Not sure how to use the same method like in the BroadcastListener without duplicating the code.
38-
return $em->getClassMetadata($entityClass)->getIdentifierValues($entity);
35+
if (null !== ($id = $this->doctrineIdAccessor->getEntityId($entity))) {
36+
return $id;
3937
}
4038

4139
if ($this->propertyAccessor) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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\Turbo\Broadcaster;
13+
14+
/**
15+
* Formats an id array to a string.
16+
*
17+
* In defaults the id array is something like `['id' => 1]` or `['uuid' => '00000000-0000-0000-0000-000000000000']`.
18+
* For a composite key it could be something like `['cart' => ['id' => 1], 'product' => ['id' => 1]]`.
19+
*
20+
* To create a string representation of the id, the values of the array are flattened and concatenated with a dash.
21+
*
22+
* @author Jason Schilling <[email protected]>
23+
*/
24+
class IdFormatter
25+
{
26+
public function format(array $id): string
27+
{
28+
$flatten = [];
29+
30+
array_walk_recursive($id, static function ($item) use (&$flatten) { $flatten[] = $item; });
31+
32+
return implode('-', $flatten);
33+
}
34+
}

src/Turbo/src/Broadcaster/TwigBroadcaster.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,24 @@ final class TwigBroadcaster implements BroadcasterInterface
2525
private $twig;
2626
private $templatePrefixes;
2727
private $idAccessor;
28+
private $idFormatter;
2829

2930
/**
3031
* @param array<string, string> $templatePrefixes
3132
*/
32-
public function __construct(BroadcasterInterface $broadcaster, Environment $twig, array $templatePrefixes = [], ?IdAccessor $idAccessor = null)
33+
public function __construct(BroadcasterInterface $broadcaster, Environment $twig, array $templatePrefixes = [], ?IdAccessor $idAccessor = null, ?IdFormatter $idFormatter = null)
3334
{
3435
$this->broadcaster = $broadcaster;
3536
$this->twig = $twig;
3637
$this->templatePrefixes = $templatePrefixes;
3738
$this->idAccessor = $idAccessor ?? new IdAccessor();
39+
$this->idFormatter = $idFormatter ?? new IdFormatter();
3840
}
3941

4042
public function broadcast(object $entity, string $action, array $options): void
4143
{
4244
if (!isset($options['id']) && null !== $id = $this->idAccessor->getEntityId($entity)) {
4345
$options['id'] = $id;
44-
$options['id_formatted'] = $id;
4546
}
4647

4748
$class = ClassUtil::getEntityClass($entity);
@@ -64,7 +65,7 @@ public function broadcast(object $entity, string $action, array $options): void
6465
->renderBlock($action, [
6566
'entity' => $entity,
6667
'action' => $action,
67-
'id' => $options['id_formatted'],
68+
'id' => $this->idFormatter->format($options['id'] ?? []),
6869
] + $options);
6970

7071
$this->broadcaster->broadcast($entity, $action, $options);

src/Turbo/src/Doctrine/BroadcastListener.php

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use Symfony\Contracts\Service\ResetInterface;
2020
use Symfony\UX\Turbo\Attribute\Broadcast;
2121
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
22+
use Symfony\UX\Turbo\Broadcaster\DoctrineIdAccessor;
23+
use Symfony\UX\Turbo\Broadcaster\IdAccessor;
2224

2325
/**
2426
* Detects changes made from Doctrine entities and broadcasts updates to the broadcasters.
@@ -29,6 +31,7 @@ final class BroadcastListener implements ResetInterface
2931
{
3032
private $broadcaster;
3133
private $annotationReader;
34+
private $doctrineIdAccessor;
3235

3336
/**
3437
* @var array<class-string, array<mixed>>
@@ -48,12 +51,13 @@ final class BroadcastListener implements ResetInterface
4851
*/
4952
private $removedEntities;
5053

51-
public function __construct(BroadcasterInterface $broadcaster, ?Reader $annotationReader = null)
54+
public function __construct(BroadcasterInterface $broadcaster, ?Reader $annotationReader = null, ?DoctrineIdAccessor $doctrineIdAccessor = null)
5255
{
5356
$this->reset();
5457

5558
$this->broadcaster = $broadcaster;
5659
$this->annotationReader = $annotationReader;
60+
$this->doctrineIdAccessor = $doctrineIdAccessor ?? new DoctrineIdAccessor();
5761
}
5862

5963
/**
@@ -94,10 +98,9 @@ public function postFlush(EventArgs $eventArgs): void
9498
try {
9599
foreach ($this->createdEntities as $entity) {
96100
$options = $this->createdEntities[$entity];
97-
$id = $this->getIdentifierValues($em, $entity);
101+
$id = $this->doctrineIdAccessor->getEntityId($entity);
98102
foreach ($options as $option) {
99103
$option['id'] = $id;
100-
$options['id_formatted'] = $this->formatId($id);
101104
$this->broadcaster->broadcast($entity, Broadcast::ACTION_CREATE, $option);
102105
}
103106
}
@@ -148,37 +151,13 @@ private function storeEntitiesToPublish(EntityManagerInterface $em, object $enti
148151

149152
if ($options = $this->broadcastedClasses[$class]) {
150153
if ('createdEntities' !== $property) {
151-
$id = $this->getIdentifierValues($em, $entity);
154+
$id = $this->doctrineIdAccessor->getEntityId($entity);
152155
foreach ($options as $k => $option) {
153156
$options[$k]['id'] = $id;
154-
$options[$k]['id_formatted'] = $this->formatId($id);
155157
}
156158
}
157159

158160
$this->{$property}->attach($entity, $options);
159161
}
160162
}
161-
162-
private function getIdentifierValues(EntityManagerInterface $em, object $entity): array
163-
{
164-
$class = ClassUtil::getEntityClass($entity);
165-
166-
$values = $em->getClassMetadata($class)->getIdentifierValues($entity);
167-
168-
foreach ($values as $key => $value) {
169-
if (\is_object($value)) {
170-
$values[$key] = $this->getIdentifierValues($em, $value);
171-
}
172-
}
173-
174-
return $values;
175-
}
176-
177-
private function formatId(array $id): string
178-
{
179-
$flatten = [];
180-
array_walk_recursive($id, static function ($item) use (&$flatten) { $flatten[] = $item; });
181-
182-
return implode('-', $flatten);
183-
}
184163
}

src/Turbo/tests/app/Kernel.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ public function cartProducts(Request $request, EntityManagerInterface $doctrine,
319319

320320
if (!$cartId || !$productId) {
321321
$cart = new Cart();
322-
323322
$product = new Product();
324323

325324
if ($title = $request->get('title')) {

0 commit comments

Comments
 (0)