From 298229e56ea1279318ce5d15f549d9d8f32b963d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 7 Jul 2022 00:10:53 +0200 Subject: [PATCH] [Turbo] Make the Broadcast attribute repeatable --- src/Turbo/Attribute/Broadcast.php | 2 +- src/Turbo/CHANGELOG.md | 3 +- src/Turbo/Doctrine/BroadcastListener.php | 40 +++++++++++++++++------- src/Turbo/Resources/doc/index.rst | 8 ++++- src/Turbo/Tests/app/Entity/Song.php | 2 +- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/Turbo/Attribute/Broadcast.php b/src/Turbo/Attribute/Broadcast.php index 749e2ad28d8..ac7d605c1c2 100644 --- a/src/Turbo/Attribute/Broadcast.php +++ b/src/Turbo/Attribute/Broadcast.php @@ -23,7 +23,7 @@ * * @experimental */ -#[\Attribute(\Attribute::TARGET_CLASS)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] final class Broadcast { public const ACTION_CREATE = 'create'; diff --git a/src/Turbo/CHANGELOG.md b/src/Turbo/CHANGELOG.md index cbef8cbc654..f1392bc10ea 100644 --- a/src/Turbo/CHANGELOG.md +++ b/src/Turbo/CHANGELOG.md @@ -2,7 +2,8 @@ ## 2.2 -- The topics defined in the Broadcast attribute now support expression language when prefixed with `@=`. +- The topics defined in the `Broadcast` attribute now support expression language when prefixed with `@=`. +- The `Broadcast` attribute can now be repeated, this is convenient to render several Turbo Streams Twig templates for the same change ## 2.1 diff --git a/src/Turbo/Doctrine/BroadcastListener.php b/src/Turbo/Doctrine/BroadcastListener.php index a6da10ddc0e..c5f0fa03d92 100644 --- a/src/Turbo/Doctrine/BroadcastListener.php +++ b/src/Turbo/Doctrine/BroadcastListener.php @@ -33,7 +33,7 @@ final class BroadcastListener implements ResetInterface private $annotationReader; /** - * @var array + * @var array */ private $broadcastedClasses; @@ -96,16 +96,23 @@ public function postFlush(EventArgs $eventArgs): void try { foreach ($this->createdEntities as $entity) { $options = $this->createdEntities[$entity]; - $options['id'] = $em->getClassMetadata(\get_class($entity))->getIdentifierValues($entity); - $this->broadcaster->broadcast($entity, Broadcast::ACTION_CREATE, $options); + $id = $em->getClassMetadata(\get_class($entity))->getIdentifierValues($entity); + foreach ($options as $option) { + $option['id'] = $id; + $this->broadcaster->broadcast($entity, Broadcast::ACTION_CREATE, $option); + } } foreach ($this->updatedEntities as $entity) { - $this->broadcaster->broadcast($entity, Broadcast::ACTION_UPDATE, $this->updatedEntities[$entity]); + foreach ($this->updatedEntities[$entity] as $option) { + $this->broadcaster->broadcast($entity, Broadcast::ACTION_UPDATE, $option); + } } foreach ($this->removedEntities as $entity) { - $this->broadcaster->broadcast($entity, Broadcast::ACTION_REMOVE, $this->removedEntities[$entity]); + foreach ($this->removedEntities[$entity] as $option) { + $this->broadcaster->broadcast($entity, Broadcast::ACTION_REMOVE, $option); + } } } finally { $this->reset(); @@ -124,21 +131,30 @@ private function storeEntitiesToPublish(EntityManagerInterface $em, object $enti $class = \get_class($entity); if (!isset($this->broadcastedClasses[$class])) { - $this->broadcastedClasses[$class] = false; + $this->broadcastedClasses[$class] = []; $r = null; if (\PHP_VERSION_ID >= 80000 && $options = ($r = new \ReflectionClass($class))->getAttributes(Broadcast::class)) { - $options = $options[0]->newInstance(); - $this->broadcastedClasses[$class] = $options->options; - } elseif ($this->annotationReader && $options = $this->annotationReader->getClassAnnotation($r ?? new \ReflectionClass($class), Broadcast::class)) { - $this->broadcastedClasses[$class] = $options->options; + foreach ($options as $option) { + $this->broadcastedClasses[$class][] = $option->newInstance()->options; + } + } elseif ($this->annotationReader && $options = $this->annotationReader->getClassAnnotations($r ?? new \ReflectionClass($class))) { + foreach ($options as $option) { + if ($option instanceof Broadcast) { + $this->broadcastedClasses[$class][] = $option->options; + } + } } } - if (false !== $options = $this->broadcastedClasses[$class]) { + if ($options = $this->broadcastedClasses[$class]) { if ('createdEntities' !== $property) { - $options['id'] = $em->getClassMetadata($class)->getIdentifierValues($entity); + $id = $em->getClassMetadata($class)->getIdentifierValues($entity); + foreach ($options as $k => $option) { + $options[$k]['id'] = $id; + } } + $this->{$property}->attach($entity, $options); } } diff --git a/src/Turbo/Resources/doc/index.rst b/src/Turbo/Resources/doc/index.rst index f12dbb4074b..0f0ddd67014 100644 --- a/src/Turbo/Resources/doc/index.rst +++ b/src/Turbo/Resources/doc/index.rst @@ -620,6 +620,11 @@ The ``Broadcast`` attribute comes with a set of handy options: is derived from the FQCN of the entity and from its id - ``template`` (``string``): Twig template to render (see above) +The ``Broadcast`` attribute can be repeated. This is convenient to +to render several templates associated with their own topics for the +same change (e.g. the same data is rendered in different way in the +list and in the detail pages). + Options are transport-specific. When using Mercure, some extra options are supported: @@ -638,7 +643,8 @@ Example:: use Symfony\UX\Turbo\Attribute\Broadcast; - #[Broadcast(topics: ['@="books_by_author_" ~ entity.author?.id', 'books'], template: 'foo.stream.html.twig', private: true)] + #[Broadcast(topics: ['@="book_detail" ~ entity.id', 'books'], template: 'book_detail.stream.html.twig', private: true)] + #[Broadcast(topics: ['@="book_list" ~ entity.id', 'books'], template: 'book_list.stream.html.twig', private: true)] class Book { // ... diff --git a/src/Turbo/Tests/app/Entity/Song.php b/src/Turbo/Tests/app/Entity/Song.php index 6ca4c76298f..257d369f08b 100644 --- a/src/Turbo/Tests/app/Entity/Song.php +++ b/src/Turbo/Tests/app/Entity/Song.php @@ -40,7 +40,7 @@ class Song public $title = ''; /** - * @ORM\ManyToOne(targetEntity="App\Entity\Artist", inversedBy="songs") + * @ORM\ManyToOne(targetEntity=Artist::class, inversedBy="songs") * * @var Artist|null */