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
屬性,以防止它阻擋頁面載入。請參閱「Moving <script> inside <head> and the "defer" Attribute」以取得更多資訊。
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 Reloading When Assets Change。
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(Symfony 隨附的功能測試工具)共享相同的 API。
安裝 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 更新廣播給所有目前連線的用戶端。
為了說明這一點,讓我們建置一個聊天系統,且無需編寫任何 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 及其 IDtemplate
(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 選項(預設情況下是啟用狀態),則會自動新增這些標籤,因為這些類別實作了 BroadcasterInterface
和 TurboStreamListenRendererInterface
介面,相關的服務也會是如此。
向後相容性承諾
此套件旨在遵循與 Symfony 框架相同的向後相容性承諾:https://symfony.dev.org.tw/doc/current/contributing/code/bc.html
鳴謝
Symfony UX Turbo 由 Kévin Dunglas 建立。它受到 hotwired/turbo-rails 和 sroze/live-twig 的啟發。