Skip to content

[Turbo] Make the Broadcast attribute repeatable #387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Turbo/Attribute/Broadcast.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*
* @experimental
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
final class Broadcast
{
public const ACTION_CREATE = 'create';
Expand Down
3 changes: 2 additions & 1 deletion src/Turbo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor - but this will be for 2.3


## 2.1

Expand Down
40 changes: 28 additions & 12 deletions src/Turbo/Doctrine/BroadcastListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class BroadcastListener implements ResetInterface
private $annotationReader;

/**
* @var array<class-string, mixed[]|false>
* @var array<class-string, array[]>
*/
private $broadcastedClasses;

Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/Turbo/Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
{
// ...
Expand Down
2 changes: 1 addition & 1 deletion src/Turbo/Tests/app/Entity/Song.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down