Symfony UX Turbo
Symfony UX Turbo 是一个 Symfony 扩展包,集成了 Hotwire Turbo 库到 Symfony 应用程序中。它是 Symfony UX 倡议 的一部分。
Symfony UX Turbo 允许获得与 单页应用程序 相同的用户体验,但无需编写一行 JavaScript 代码!
Symfony UX Turbo 还集成了 Symfony Mercure 或任何其他传输方式,以将 DOM 更改广播给所有当前连接的用户!
赶时间?看看 聊天示例,了解 Symfony UX Turbo 的全部潜力。
或者观看 SymfonyCasts 上的 Turbo 视频教程。
安装
使用 Composer 和 Symfony Flex 安装扩展包
1
$ composer require symfony/ux-turbo
如果你正在使用 WebpackEncore,安装你的 assets 并重启 Encore(如果你正在使用 AssetMapper 则不需要)
1 2
$ npm install --force
$ npm run watch
用法
使用 Turbo Drive 加速导航
Turbo Drive 增强了页面级别的导航。它监视链接点击和表单提交,在后台执行它们,并在不完全重新加载的情况下更新页面。这为你提供了“单页应用”的体验,而无需对你的代码进行重大更改!
当你安装 Symfony UX Turbo 时,Turbo Drive 会自动启用。虽然你不需要进行重大更改就可以使一切顺利运行,但有 3 件事需要注意
1. 确保你的 JavaScript 已为 Turbo 做好准备
由于导航不再导致完全页面刷新,你可能需要调整你的 JavaScript 以使其正常工作。最好的解决方案是使用 Stimulus 或类似的东西来编写你的 JavaScript。
我们还建议你将 script
标签放在你的 head
标签内,以便它们不会在每次导航时重新加载(Turbo 在每次导航时重新执行 body
内的任何 script
标签)。为每个 script
标签添加 defer
属性,以防止它阻塞页面加载。请参阅 将 <script> 移动到 <head> 内以及 “defer” 属性 以获取更多信息。
2. 当 JavaScript/CSS 文件更改时重新加载
如果你的 CSS 或 JS 文件之一的内容发生更改,Turbo Drive 可以自动执行完全刷新,以确保你的用户始终拥有最新版本。
要启用此功能,首先验证你是否在 Encore 中启用了版本控制,以便你的文件名在文件内容更改时发生更改
1 2 3 4 5
// webpack.config.js
Encore.
// ...
.enableVersioning(Encore.isProduction())
然后将 data-turbo-track="reload"
属性添加到你的所有 script
和 link
标签
1 2 3 4 5 6 7 8 9
# config/packages/webpack_encore.yaml
webpack_encore:
# ...
script_attributes:
defer: true
'data-turbo-track': reload
link_attributes:
'data-turbo-track': reload
有关更多信息,请参阅:Turbo 当 Assets 更改时重新加载。
3. 表单响应代码更改
Turbo Drive 还会将表单提交转换为 AJAX 调用。为了使其工作,你确实需要调整你的代码,以便在验证错误时返回 422 状态代码(而不是 200)。
如果你正在使用 Symfony 6.2+,render()
方法会自动处理此问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#[Route('/product/new', name: 'product_new')]
public function newProduct(Request $request): Response
{
$form = $this->createForm(ProductFormType::class, null, [
'action' => $this->generateUrl('product_new'),
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// save...
return $this->redirectToRoute('product_list');
}
return $this->render('product/new.html.twig', [
'form' => $form,
]);
}
如果你没有使用 Symfony 6.2+,请手动调整你的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#[Route('/product/new')]
public function newProduct(Request $request): Response
{
$form = $this->createForm(ProductFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// save...
}
+ $response = new Response(null, $form->isSubmitted() ? 422 : 200);
return $this->render('product/new.html.twig', [
'form' => $form->createView()
- ]);
+ ], $response);
}
这会将验证错误时的响应状态代码更改为 422,这告诉 Turbo Drive 表单提交失败,它应该使用错误重新渲染。你还可以选择将成功的重定向状态代码从 302(默认值)更改为 303(HTTP_SEE_OTHER
)。这对于 Turbo Drive 不是必需的,但 303 在这种情况下“更正确”。
注意
注意: 当你的表单包含多个提交按钮,并且你想检查单击了哪个按钮以调整控制器中的程序流程时。你需要为每个按钮添加一个值,因为 Turbo Drive 不会发送值为空的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$builder
// ...
->add('save', SubmitType::class, [
'label' => 'Create Task',
'attr' => [
'value' => 'create-task'
]
])
->add('saveAndAdd', SubmitType::class, [
'label' => 'Save and Add',
'attr' => [
'value' => 'save-and-add'
]
]);
更多 Turbo Drive 信息
阅读 Turbo Drive 文档,了解 Turbo Drive 提供的高级功能。
使用 Turbo Frames 分解复杂页面
安装 Symfony UX Turbo 后,你还可以利用 Turbo Frames
1 2 3 4 5 6 7 8
{# home.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<turbo-frame id="the_frame_id">
<a href="{{ path('another-page') }}">This block is scoped, the rest of the page will not change if you click here!</a>
</turbo-frame>
{% endblock %}
1 2 3 4 5 6 7 8 9 10 11
{# another-page.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div>This will be discarded</div>
<turbo-frame id="the_frame_id">
The content of this block will replace the content of the Turbo Frame!
The rest of the HTML generated by this template (outside of the Turbo Frame) will be ignored.
</turbo-frame>
{% endblock %}
帧的内容可以延迟加载
1 2 3 4 5 6 7 8
{# home.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<turbo-frame id="the_frame_id" src="{{ path('block') }}">
A placeholder.
</turbo-frame>
{% endblock %}
在你的控制器中,你可以检测请求是否由 Turbo Frame 触发,并检索此帧的 ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/MyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MyController
{
#[Route('/')]
public function home(Request $request): Response
{
// Get the frame ID (will be null if the request hasn't been triggered by a Turbo Frame)
$frameId = $request->headers->get('Turbo-Frame');
// ...
}
}
<twig:Turbo:Frame> Twig 组件
2.22
<twig:Turbo:Frame>
Twig 组件是在 Turbo 2.22 中添加的。
简单示例
1 2 3 4
<twig:Turbo:Frame id="the_frame_id" />
{# renders as: #}
<turbo-frame id="the_frame_id"></turbo-frame>
使用 HTML 属性
1 2 3 4
<twig:Turbo:Frame id="the_frame_id" loading="lazy" src="{{ path('block') }}" />
{# renders as: #}
<turbo-frame id="the_frame_id" loading="lazy" src="https://example.com/block"></turbo-frame>
包含内容
1 2 3 4 5 6 7 8
<twig:Turbo:Frame id="the_frame_id" src="{{ path('block') }}">
A placeholder.
</twig:Turbo:Frame>
{# renders as: #}
<turbo-frame id="the_frame_id" src="https://example.com/block">
A placeholder.
</turbo-frame>
编写测试
在底层,Symfony UX Turbo 依赖 JavaScript 来更新 HTML 页面。要测试你的网站是否正常工作,你将必须编写 UI 测试。
幸运的是,我们已经为你考虑到了!Symfony Panther 是一个方便的测试工具,它使用真实的浏览器来测试你的 Symfony 应用程序。它与 BrowserKit 共享相同的 API,BrowserKit 是 Symfony 附带的功能测试工具。
安装 Symfony Panther 并为我们的 Turbo Frame 编写一个测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// tests/TurboFrameTest.php
namespace App\Tests;
use Symfony\Component\Panther\PantherTestCase;
class TurboFrameTest extends PantherTestCase
{
public function testFrame(): void
{
$client = self::createPantherClient();
$client->request('GET', '/');
$client->clickLink('This block is scoped, the rest of the page will not change if you click here!');
$this->assertSelectorWillContain('body', 'This will replace the content of the Turbo Frame!');
}
}
运行 bin/phpunit
来执行测试!Symfony Panther 使用 Web 服务器自动启动你的应用程序,并使用 Google Chrome 或 Firefox 对其进行测试!
你甚至可以使用以下命令在浏览器中观看更改的发生:PANTHER_NO_HEADLESS=1 bin/phpunit --debug
阅读 Turbo Frames 文档,了解你可以使用 Turbo Frames 完成的所有操作。
使用 Turbo Streams 实现动态交互
Turbo Streams 是服务器向客户端发送部分页面更新的一种方式。有两种主要方式接收更新
表单
让我们来了解如何使用 Turbo Streams 来增强你的 Symfony 表单
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39
// src/Controller/TaskController.php
namespace App\Controller;
// ...
use App\Entity\Task;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\UX\Turbo\TurboBundle;
class TaskController extends AbstractController
{
public function new(Request $request): Response
{
$form = $this->createForm(TaskType::class, new Task());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$task = $form->getData();
// ... perform some action, such as saving the task to the database
// 🔥 The magic happens here! 🔥
if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
// If the request comes from Turbo, set the content type as text/vnd.turbo-stream.html and only send the HTML to update
$request->setRequestFormat(TurboBundle::STREAM_FORMAT);
return $this->renderBlock('task/new.html.twig', 'success_stream', ['task' => $task]);
}
// If the client doesn't support JavaScript, or isn't using Turbo, the form still works as usual.
// Symfony UX Turbo is all about progressively enhancing your applications!
return $this->redirectToRoute('task_success', [], Response::HTTP_SEE_OTHER);
}
return $this->render('task/new.html.twig', [
'form' => $form,
]);
}
}
1 2 3 4 5 6 7 8 9 10
{# bottom of new.html.twig #}
{% block success_stream %}
<turbo-stream action="replace" targets="#my_div_id">
<template>
The element having the id "my_div_id" will be replaced by this block, without a full page reload!
<div>The task "{{ task }}" has been created!</div>
</template>
</turbo-stream>
{% endblock %}
支持的操作包括 append
、prepend
、replace
、update
、remove
、before
、after
和 refresh
。阅读 Turbo Streams 文档以获取更多详细信息。
重置表单
当你返回 Turbo stream 时,只有该 stream 模板中的元素将被更新。这意味着如果你想重置表单,你需要在 stream 模板中包含一个新的表单。
为此,首先将你的表单渲染隔离到一个块中,以便你可以重用它
1 2 3 4
{# new.html.twig #}
+{% block task_form %}
{{ form(form) }}
+{% endblock %}
现在,创建一个“新的”表单并将其传递到你的 stream 中
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 26 27 28 29 30 31 32 33
// src/Controller/TaskController.php
// ...
class TaskController extends AbstractController
{
public function new(Request $request): Response
{
$form = $this->createForm(TaskType::class, new Task());
+ $emptyForm = clone $form;
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// ...
if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
$request->setRequestFormat(TurboBundle::STREAM_FORMAT);
return $this->renderBlock('task/new.html.twig', 'success_stream', [
'task' => $task,
+ 'form' => $emptyForm,
]);
}
// ...
return $this->redirectToRoute('task_success', [], Response::HTTP_SEE_OTHER);
}
return $this->render('task/new.html.twig', [
'form' => $form,
]);
}
}
现在,在你的 stream 模板中,“替换”整个表单
1 2 3 4 5 6 7 8
{# new.html.twig #}
{% block success_stream %}
+<turbo-stream action="replace" targets="form[name=task]">
+ <template>
+ {{ block('task_form') }}
+ </template>
+</turbo-stream>
<turbo-stream action="replace" targets="#my_div_id">
使用 Mercure 发送异步更改:聊天室
Symfony UX Turbo 还支持使用 Mercure 协议或任何其他协议,将 HTML 更新广播到所有当前连接的客户端。
为了说明这一点,让我们构建一个0 行 JavaScript 代码的聊天系统!
首先在你的项目上安装 Mercure 支持
1
$ composer require symfony/mercure-bundle
然后,在 assets/controllers.json
中启用 “mercure stream” 控制器
1 2 3 4 5 6 7
"@symfony/ux-turbo": {
"mercure-turbo-stream": {
+ "enabled": true,
- "enabled": false,
"fetch": "eager"
}
},
拥有一个可用的开发(和生产就绪)环境的最简单方法是使用 Symfony Docker,它自带一个集成在 Web 服务器中的 Mercure hub。
如果你使用 Symfony Flex,则已为你生成了配置,请务必更新 .env
文件中的 MERCURE_URL
以指向 Mercure Hub(如果你正在使用 Symfony Docker,则不需要)。
否则,请按照文档中的说明配置 Mercure Hub(s)
1 2 3 4 5 6 7 8 9
# config/packages/mercure.yaml
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: '*'
让我们创建我们的聊天室
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// src/Controller/ChatController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
class ChatController extends AbstractController
{
public function chat(Request $request, HubInterface $hub): Response
{
$form = $this->createFormBuilder()
->add('message', TextType::class, ['attr' => ['autocomplete' => 'off']])
->add('send', SubmitType::class)
->getForm();
$emptyForm = clone $form; // Used to display an empty form after a POST request
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// 🔥 The magic happens here! 🔥
// The HTML update is pushed to the client using Mercure
$hub->publish(new Update(
'chat',
$this->renderView('chat/message.stream.html.twig', ['message' => $data['message']])
));
// Force an empty form to be rendered below
// It will replace the content of the Turbo Frame after a post
$form = $emptyForm;
}
return $this->render('chat/index.html.twig', [
'form' => $form,
]);
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
{# chat/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Chat</h1>
<div id="messages" {{ turbo_stream_listen('chat') }}>
{#
The messages will be displayed here.
"turbo_stream_listen()" automatically registers a Stimulus controller that subscribes to the "chat" topic as managed by the transport.
All connected users will receive the new messages!
#}
</div>
<turbo-frame id="message_form">
{{ form(form) }}
{#
The form is displayed in a Turbo Frame, with this trick a new empty form is displayed after every post,
but the rest of the page will not change.
#}
</turbo-frame>
{% endblock %}
1 2 3 4 5 6 7
{# chat/message.stream.html.twig #}
{# New messages received through the Mercure connection are appended to the div with the "messages" ID. #}
<turbo-stream action="append" targets="#messages">
<template>
<div>{{ message }}</div>
</template>
</turbo-stream>
请记住,你可以使用 Symfony Mercure 提供的所有功能,包括 私有更新(以确保只有授权用户会收到更新)和 使用 Symfony Messenger 进行异步调度。
广播 Doctrine 实体更新
Symfony UX Turbo 还附带了与 Doctrine ORM 的便捷集成。
使用单个属性,你的客户端可以订阅实体的创建、更新和删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// src/Entity/Book.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\UX\Turbo\Attribute\Broadcast;
#[ORM\Entity]
#[Broadcast] // 🔥 The magic happens here
class Book
{
#[ORM\Column, ORM\Id, ORM\GeneratedValue(strategy: "AUTO")]
public ?int $id = null;
#[ORM\Column]
public string $title = '';
}
要订阅实体的更新,请将其作为 turbo_stream_listen()
Twig 助手的参数传递
1
<div id="book_{{ book.id }}" {{ turbo_stream_listen(book) }}></div>
或者,你可以通过使用其完全限定类名来订阅对给定类的所有实体进行的更新
1
<div id="books" {{ turbo_stream_listen('App\\Entity\\Book') }}></div>
最后,创建将在实体创建、修改或删除时渲染的模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
{# templates/broadcast/Book.stream.html.twig #}
{% block create %}
<turbo-stream action="append" targets="#books">
<template>
<div id="{{ 'book_' ~ id }}">{{ entity.title }} (#{{ id }})</div>
</template>
</turbo-stream>
{% endblock %}
{% block update %}
<turbo-stream action="update" targets="#book_{{ id }}">
<template>
{{ entity.title }} (#{{ id }}, updated)
</template>
</turbo-stream>
{% endblock %}
{% block remove %}
<turbo-stream action="remove" targets="#book_{{ id }}"></turbo-stream>
{% endblock %}
按照约定,Symfony UX Turbo 将查找名为 templates/broadcast/{ClassName}.stream.html.twig
的模板。此模板必须至少包含 3 个块:create
、update
和 remove
(它们可以为空,但必须存在)。
每次标记有 Broadcast
属性的实体发生更改时,Symfony UX Turbo 将渲染关联的模板,并将更改广播给所有连接的客户端。
每个块必须包含 Turbo Stream 操作列表。这些操作将由 Turbo 自动应用于每个连接客户端的 DOM 树。每个模板可以包含任意数量的操作。
例如,如果同一个实体显示在不同的页面上,你可以在模板中包含更新这些不同位置的所有操作。应用于不存在的 DOM 元素的操作将被简单地忽略。
当前实体、其标识符的字符串表示形式、操作(create
、update
或 remove
)以及在 Broadcast
属性上设置的选项将作为变量传递给模板:entity
、id
、action
和 options
。
广播约定和配置
由于 Symfony UX Turbo 需要访问其标识符,因此实体必须由 Doctrine ORM 管理,具有名为 id
的公共属性,或具有名为 getId()
的公共方法。
Symfony UX Turbo 将查找以其完全限定类名命名的模板。例如,默认情况下,如果标记有 Broadcast
属性的类名为 App\Entity\Foo
,则相应的模板将在 templates/broadcast/Foo.stream.html.twig
中找到。
可以使用 turbo.broadcast.entity_template_prefixes
配置选项配置自己的命名空间映射到模板。默认值定义如下
1 2 3 4 5
# config/packages/turbo.yaml
turbo:
broadcast:
entity_template_prefixes:
App\Entity\: broadcast/
最后,也可以使用 Broadcast
属性的 template
参数显式设置要使用的模板
1 2
#[Broadcast(template: 'my-template.stream.html.twig')]
class Book { /* ... */ }
广播选项
Broadcast
属性带有一组方便的选项
transports
(string[]
):要广播到的传输方式列表topics
(string[]
):要使用的主题列表,默认主题从实体的 FQCN 及其 id 派生而来template
(string
):要渲染的 Twig 模板(见上文)
Broadcast
属性可以重复(例如,你可以有多个 `#[Broadcast]`。这对于为同一更改渲染与它们自己的主题关联的多个模板非常方便(例如,相同的数据以不同的方式在列表页和详细信息页中渲染)。
选项是特定于传输方式的。当使用 Mercure 时,支持一些额外的选项
private
(bool
):将 Mercure 更新标记为私有sse_id
(string
):SSE 的id
字段sse_type
(string
):SSE 的type
字段sse_retry
(int
):SSE 的retry
字段
Mercure 广播器还支持在以 `@=` 开头的主题中使用 Expression Language。
示例
1 2 3 4 5 6 7 8 9 10 11
// src/Entity/Book.php
namespace App\Entity;
use Symfony\UX\Turbo\Attribute\Broadcast;
#[Broadcast(topics: ['@="book_detail" ~ entity.getId()', 'books'], template: 'book_detail.stream.html.twig', private: true)]
#[Broadcast(topics: ['@="book_list" ~ entity.getId()', 'books'], template: 'book_list.stream.html.twig', private: true)]
class Book
{
// ...
}
使用多种传输方式
Symfony UX Turbo 允许使用多种传输方式发送 Turbo Streams 更新。例如,可以使用以下配置将多个 Mercure hub 一起使用
1 2 3 4 5 6 7 8 9
# config/packages/mercure.yaml
mercure:
hubs:
hub1:
url: https://hub1.example.net/.well-known/mercure
jwt: snip
hub2:
url: https://hub2.example.net/.well-known/mercure
jwt: snip
使用适当的 Mercure HubInterface
服务,以使用特定的传输方式发送更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Controller/MyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
class MyController extends AbstractController
{
public function publish(HubInterface $hub1): Response
{
$id = $hub1->publish(new Update('topic', 'content'));
return new Response("Update #{$id} published.");
}
}
默认情况下,对标记有 #[Broadcast]
属性的实体所做的更改将使用所有配置的传输方式发送。你可以使用 transports
参数指定用于特定实体类的传输方式列表
1 2 3 4 5 6 7 8 9 10 11
// src/Entity/Book.php
namespace App\Entity;
use Symfony\UX\Turbo\Attribute\Broadcast;
#[Broadcast(transports: ['hub1', 'hub2'])]
/** ... */
class Book
{
// ...
}
最后,通过将额外的参数传递给 turbo_stream_listen()
来生成 HTML 属性,注册与你的传输方式对应的 Stimulus 控制器
1
<div id="messages" {{ turbo_stream_listen('App\\Entity\\Book', 'hub2') }}></div>
注册自定义传输方式
如果你喜欢使用 Mercure 以外的其他协议,你可以创建自定义传输方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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
{
// This method will be called every time an object marked with the #[Broadcast] attribute is changed
$attribute = (new \ReflectionClass($entity))->getAttributes(Broadcast::class)[0] ?? null;
// ...
}
}
然后是一个 stream 监听器
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/TurboStreamListenRenderer.php
namespace App\Turbo;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
use Symfony\UX\Turbo\Twig\TurboStreamListenRendererInterface;
use Twig\Environment;
#[AsTaggedItem(index: 'my-transport')]
class TurboStreamListenRenderer implements TurboStreamListenRendererInterface
{
public function __construct(
private StimulusHelper $stimulusHelper,
) {}
public function renderTurboStreamListen(Environment $env, $topic): string
{
$stimulusAttributes = $this->stimulusHelper->createStimulusAttributes();
$stimulusAttributes->addController('your_stimulus_controller', [
/* controller values such as topic */
]);
return (string) $stimulusAttributes;
}
}
广播器必须注册为标记为 turbo.broadcaster
的服务,渲染器必须注册为标记为 turbo.renderer.stream_listen
的服务。如果你启用了 autoconfigure option (默认情况下就是这种情况),则会自动添加这些标签,因为这些类实现了 BroadcasterInterface
和 TurboStreamListenRendererInterface
接口,相关的服务也将被添加。
向后兼容性承诺
此扩展包旨在遵循与 Symfony 框架相同的向后兼容性承诺:http://symfony.ac.cn/doc/current/contributing/code/bc.html
鸣谢
Symfony UX Turbo 由 Kévin Dunglas 创建。它的灵感来源于 hotwired/turbo-rails 和 sroze/live-twig。