Skip to content

How to Register a Custom Transport

Edit this page

If you prefer using another protocol than Mercure, you can create custom transports:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Turbo/Broadcaster.php
namespace App\Turbo;

use Symfony\UX\Turbo\Attribute\Broadcast;
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;

class Broadcaster implements BroadcasterInterface
{
    public function broadcast(object $entity, string $action): void
    {
        // Called every time an object marked with #[Broadcast] changes
        $attribute = (new \ReflectionClass($entity))
            ->getAttributes(Broadcast::class)[0] ?? null;
        // ...
    }
}

Then a stream source renderer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Turbo/MyStreamSourceRenderer.php
namespace App\Turbo;

use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
use Symfony\UX\Turbo\StreamSourceRendererInterface;

#[AsTaggedItem(index: 'my-transport')]
class MyStreamSourceRenderer implements StreamSourceRendererInterface
{
    public function render(string|object|array $topics, array $options = []): string
    {
        // $topics is a string, an object (e.g. a Doctrine entity) or an array
        // of them; convert them into the topic names used by your broadcaster
        $url = 'https://my-transport.example.com/subscribe?'.http_build_query([
            'topic' => $this->resolveTopics($topics),
        ]);
        $private = $options['private'] ?? false;

        return \sprintf(
            '<my-custom-stream-source src="%s"%s></my-custom-stream-source>',
            htmlspecialchars($url, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'),
            $private ? ' private' : '',
        );
    }
}

The broadcaster must be registered as a service tagged with turbo.broadcaster and the stream source renderer must be tagged with turbo.stream_source_renderer. If you enabled autoconfigure option (it's the case by default), these tags will be added automatically because these classes implement the BroadcasterInterface and StreamSourceRendererInterface interfaces.

The index of the stream source renderer (my-transport in the example) is the name that templates pass to select the transport:

1
{{ turbo_stream_from('tasks', transport: 'my-transport') }}

The renderer only outputs the HTML element that connects the page to your transport. You also need to write the JavaScript of that element (e.g. a custom element) that receives the messages and passes them to Turbo with its connectStreamSource() function.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version