diff --git a/src/Turbo/CHANGELOG.md b/src/Turbo/CHANGELOG.md index 012b15f241e..5a0f7d0f4f5 100644 --- a/src/Turbo/CHANGELOG.md +++ b/src/Turbo/CHANGELOG.md @@ -5,6 +5,7 @@ - Add `` component - Add `` component - Add support for custom actions in `TurboStream` and `TurboStreamResponse` +- Add support for providing multiple mercure topics to `turbo_stream_listen` ## 2.21.0 diff --git a/src/Turbo/assets/dist/turbo_stream_controller.d.ts b/src/Turbo/assets/dist/turbo_stream_controller.d.ts index a86c6796863..2806afea3cc 100644 --- a/src/Turbo/assets/dist/turbo_stream_controller.d.ts +++ b/src/Turbo/assets/dist/turbo_stream_controller.d.ts @@ -2,14 +2,17 @@ import { Controller } from '@hotwired/stimulus'; export default class extends Controller { static values: { topic: StringConstructor; + topics: ArrayConstructor; hub: StringConstructor; }; es: EventSource | undefined; url: string | undefined; readonly topicValue: string; + readonly topicsValue: string[]; readonly hubValue: string; readonly hasHubValue: boolean; readonly hasTopicValue: boolean; + readonly hasTopicsValue: boolean; initialize(): void; connect(): void; disconnect(): void; diff --git a/src/Turbo/assets/dist/turbo_stream_controller.js b/src/Turbo/assets/dist/turbo_stream_controller.js index 287dbbf7719..3d55567c772 100644 --- a/src/Turbo/assets/dist/turbo_stream_controller.js +++ b/src/Turbo/assets/dist/turbo_stream_controller.js @@ -6,12 +6,19 @@ class default_1 extends Controller { const errorMessages = []; if (!this.hasHubValue) errorMessages.push('A "hub" value pointing to the Mercure hub must be provided.'); - if (!this.hasTopicValue) - errorMessages.push('A "topic" value must be provided.'); + if (!this.hasTopicValue && !this.hasTopicsValue) + errorMessages.push('Either "topic" or "topics" value must be provided.'); if (errorMessages.length) throw new Error(errorMessages.join(' ')); const u = new URL(this.hubValue); - u.searchParams.append('topic', this.topicValue); + if (this.hasTopicValue) { + u.searchParams.append('topic', this.topicValue); + } + else { + this.topicsValue.forEach((topic) => { + u.searchParams.append('topic', topic); + }); + } this.url = u.toString(); } connect() { @@ -29,6 +36,7 @@ class default_1 extends Controller { } default_1.values = { topic: String, + topics: Array, hub: String, }; diff --git a/src/Turbo/assets/src/turbo_stream_controller.ts b/src/Turbo/assets/src/turbo_stream_controller.ts index c408dcfb099..4c8fd4d915a 100644 --- a/src/Turbo/assets/src/turbo_stream_controller.ts +++ b/src/Turbo/assets/src/turbo_stream_controller.ts @@ -16,24 +16,34 @@ import { connectStreamSource, disconnectStreamSource } from '@hotwired/turbo'; export default class extends Controller { static values = { topic: String, + topics: Array, hub: String, }; es: EventSource | undefined; url: string | undefined; declare readonly topicValue: string; + declare readonly topicsValue: string[]; declare readonly hubValue: string; declare readonly hasHubValue: boolean; declare readonly hasTopicValue: boolean; + declare readonly hasTopicsValue: boolean; initialize() { const errorMessages: string[] = []; if (!this.hasHubValue) errorMessages.push('A "hub" value pointing to the Mercure hub must be provided.'); - if (!this.hasTopicValue) errorMessages.push('A "topic" value must be provided.'); + if (!this.hasTopicValue && !this.hasTopicsValue) + errorMessages.push('Either "topic" or "topics" value must be provided.'); if (errorMessages.length) throw new Error(errorMessages.join(' ')); const u = new URL(this.hubValue); - u.searchParams.append('topic', this.topicValue); + if (this.hasTopicValue) { + u.searchParams.append('topic', this.topicValue); + } else { + this.topicsValue.forEach((topic) => { + u.searchParams.append('topic', topic); + }); + } this.url = u.toString(); } diff --git a/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php b/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php index 89f24c29e9e..8e8c44311e5 100644 --- a/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php +++ b/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php @@ -44,23 +44,34 @@ public function __construct( public function renderTurboStreamListen(Environment $env, $topic): string { - if (\is_object($topic)) { - $class = $topic::class; + $topics = []; - if (!$id = $this->idAccessor->getEntityId($topic)) { - throw new \LogicException(\sprintf('Cannot listen to entity of class "%s" as the PropertyAccess component is not installed. Try running "composer require symfony/property-access".', $class)); + foreach ((array) $topic as $topicVal) { + if (\is_object($topicVal)) { + $class = $topicVal::class; + + if (!$id = $this->idAccessor->getEntityId($topicVal)) { + throw new \LogicException(\sprintf('Cannot listen to entity of class "%s" as the PropertyAccess component is not installed. Try running "composer require symfony/property-access".', $class)); + } + + $topics[] = \sprintf(Broadcaster::TOPIC_PATTERN, rawurlencode($class), rawurlencode(implode('-', $id))); + } elseif (!preg_match('/[^a-zA-Z0-9_\x7f-\xff\\\\]/', $topic) && class_exists($topicVal)) { + // Generate a URI template to subscribe to updates for all objects of this class + $topics[] = \sprintf(Broadcaster::TOPIC_PATTERN, rawurlencode($topicVal), '{id}'); } + } - $topic = \sprintf(Broadcaster::TOPIC_PATTERN, rawurlencode($class), rawurlencode(implode('-', $id))); - } elseif (!preg_match('/[^a-zA-Z0-9_\x7f-\xff\\\\]/', $topic) && class_exists($topic)) { - // Generate a URI template to subscribe to updates for all objects of this class - $topic = \sprintf(Broadcaster::TOPIC_PATTERN, rawurlencode($topic), '{id}'); + $controllerAttributes = ['hub' => $this->hub->getPublicUrl()]; + if (1 < \count($topics)) { + $controllerAttributes['topics'] = $topics; + } else { + $controllerAttributes['topic'] = current($topics); } $stimulusAttributes = $this->stimulusHelper->createStimulusAttributes(); $stimulusAttributes->addController( 'symfony/ux-turbo/mercure-turbo-stream', - ['topic' => $topic, 'hub' => $this->hub->getPublicUrl()] + $controllerAttributes, ); return (string) $stimulusAttributes; diff --git a/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php b/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php index 3670e40bd28..0bacd2cda8a 100644 --- a/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php +++ b/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php @@ -21,7 +21,7 @@ interface TurboStreamListenRendererInterface { /** - * @param string|object $topic + * @param string|object|array $topic */ public function renderTurboStreamListen(Environment $env, $topic): string; } diff --git a/src/Turbo/src/Twig/TwigExtension.php b/src/Turbo/src/Twig/TwigExtension.php index 87dc3fe58fc..570adfb7d86 100644 --- a/src/Turbo/src/Twig/TwigExtension.php +++ b/src/Turbo/src/Twig/TwigExtension.php @@ -35,7 +35,7 @@ public function getFunctions(): array } /** - * @param object|string $topic + * @param object|string|array $topic */ public function turboStreamListen(Environment $env, $topic, ?string $transport = null): string {