廣播 - Broadcast

簡介

在許多現代 Web App 中,都使用了 WebSocket 來提供即時更新的 UI。當某個資料在伺服器上被更新,通常會通過 WebSocket 連線來將一個訊息傳送給用戶端做處理。比起不斷從伺服器上拉取資料並反應到 UI 上,WebSocket 提供是更有效率的方案。

舉例來說,假設我們的 App 可以將使用者資料匯出為 CSV 檔並以電子郵件寄出。不過,建立 CSV 檔需要數分鐘的事件,因此我們選擇將建立與寄送 CSV 檔的程式放在佇列任務中。當 CSV 當建立完畢並寄給使用者後,我們可以使用「事件廣播」來將 App\Events\UserDataExported 事件分派給應用程式的 JavaScript。收到事件後,使用者就能在不重新整理的情況下看到一個訊息,表示我們已將 CSV 檔寄送出去。

為了協助你製作這種類型的功能,Laravel 讓你能簡單地將伺服器端 Laravel 事件通過 WebSocket 連線來「廣播」出去。通過廣播 Laravel 事件,就可以在伺服器端 Laravel 程式與用戶端 JavaScript 程式間共享相同的事件名稱與資料。

「廣播」背後的核心概念很簡單:用戶端會在前端連線到一個有名稱的頻道,而後端 Laravel 網站則會將事件廣播給這些頻道。這些事件可以包含任何你想讓前端存取的額外資料。

支援的 Driver

預設情況下,Laravel 包含了兩個伺服器端廣播 Driver 可供選擇:Pusher ChannelsAbly。不過,也有如 laravel-websocketssoketi 這樣由社群開發的套件提供不需要商業 Broadcast Provider 的額外 Broadcast Driver。

lightbulb

在深入探討事件廣播前,請先確保你已閱讀有關 事件與監聽程式的 Laravel 說明文件。

伺服器端安裝

若要開始使用 Laravel 的事件廣播,我們需要在 Laravel 專案中做一些設定以及安裝一些套件。

事件廣播是通過伺服器端的廣播 Driver 將 Laravel 事件廣播出去,讓 Laravel Echo (一個 JavaScript 套件) 可以在瀏覽器用戶端內接收這個事件。別擔心 —— 我們會一步一步地介紹安裝過程的每一部分。

設定

專案中,所有關於事件廣播的設定都放在 config/boradcasting.php 設定檔中。Laravel 內建支援多個 Broadcast Driver:Pusher ChannelsRedis、以及一個用於本機開發與偵錯的 log Driver。此外,也包含了一個可以在測試期間完全禁用廣播的 null Driver。config/boradcasting.php 設定中包含了各個 Driver 的設定範例。

Broadcast Service Provider

在廣播任何事件以前,需要先註冊 App\Providers\BroadcastServiceProvider。在新安裝的 Laravel 專案中,只需要在 config/app.php 設定檔內的 providers 陣列中取消註解這個 Provider 即可。這個 BroadcastServiceProvider 包含了要註冊廣播授權路由以及回呼所需的程式碼。

設定佇列

也需要註冊並執行一個佇列背景工作角色。所有的事件廣播都是通過佇列任務來完成的,這樣一來在事件被廣播的過程所需的事件才不會對網站的回應時間有太大的影響。

Pusher Channels

若有打算要使用 Pusher Channels,那麼應通過 Composer 套件管理員來安裝 Pusher Channels 的 PHP SDK:

1composer require pusher/pusher-php-server
1composer require pusher/pusher-php-server

接著,應在 config/broadcasting.php 設定檔中設定 Pusher Channels 的憑證。該檔案中已經有包含了一個範例的 Pusher Channels 設定,讓你可以快速指定你的 Key, Secret 以及 Application ID。通常來說,這些值應該要通過 PUSHER_APP_KEY, PUSHER_APP_SECRETPUSHER_APP_ID 環境變數 來設定:

1PUSHER_APP_ID=your-pusher-app-id
2PUSHER_APP_KEY=your-pusher-key
3PUSHER_APP_SECRET=your-pusher-secret
4PUSHER_APP_CLUSTER=mt1
1PUSHER_APP_ID=your-pusher-app-id
2PUSHER_APP_KEY=your-pusher-key
3PUSHER_APP_SECRET=your-pusher-secret
4PUSHER_APP_CLUSTER=mt1

config/broadcasting.php 檔的 pusher 設定能讓你指定 Channels 所支援的額外選項 options,如簇集 (Cluster)。

接著,需要在 .env 檔中更改你的 Broadcast Driver 為 pusher

1BROADCAST_DRIVER=pusher
1BROADCAST_DRIVER=pusher

最後,就可以安裝並設定 Laravel Echo。Laravel Echo 會在用戶端上接收廣播事件。

開放原始碼的 Pusher 替代

laravel-websocketssoketi 套件提供了用於 Laravel 的 Pusher 相同 WebSocket 伺服器。使用這些套件就能在不依賴商業 WebSocket Provider 的情況下使用完整的 Laravel Broadcasting 功能。有關安裝這些套件的更多資訊,請參考我們的開放原始碼替代說明文件。

Ably

lightbulb

下方的說明文件討論了如何在「Pusher 相容模式 (Pusher Compatibility)」下使用 Ably。不過,Ably 團隊推薦並維護了一個 Broadcaster 程式,以及一個可使用 Ably 特別功能的 Echo 用戶端。更多有關使用 Ably 維護的 Driver 的資訊,請參考 Ably 的 Laravel Broadcaster 說明文件 (英語)

若有打算要使用 Ably,則請使用 Composer 套件管理員來安裝 Ably 的 PHP SDK:

1composer require ably/ably-php
1composer require ably/ably-php

接著,應在 config/broadcasting.php 設定檔中設定 Pusher Channels 的憑證。該檔案中已經有包含了一個範例的 Ably 設定,讓你可以快速指定你的金鑰。通常來說,這個值應該要通過 ABLY_KEY 環境變數 來設定:

1ABLY_KEY=your-ably-key
1ABLY_KEY=your-ably-key

接著,需要在 .env 檔中更改你的 Broadcast Driver 為 ably

1BROADCAST_DRIVER=ably
1BROADCAST_DRIVER=ably

最後,就可以安裝並設定 Laravel Echo。Laravel Echo 會在用戶端上接收廣播事件。

開放原始碼替代

PHP

laravel-websockets 套件是一個純 PHP、適用於 Laravel 的 Pusher 相容 WebSocket 套件。這個套件能讓你使用 Laravel 廣播的全部功能,而無需商業 WebSocket Provider。有關安裝與使用該套件的更多資訊,請參考其官方說明文件

Node

Soketi 套件是一個基於 Node、適用於 Laravel 的 Pusher 相容 WebSocket 伺服器。從底層來看,Soketi 使用了 µWebSockets.js 來獲得最佳可擴充性與速度。Sketi 能讓你在不需仰賴商業 WebSocket Provider 的情況下使用 Laravel 廣播的全部功能。有關安裝與使用 Sketi 的更多資訊,請參考其官方說明文件

用戶端安裝

Pusher Channels

Laravel Echo 是一個 JavaScript 套件,能讓你免於煩惱如何訂閱頻道與監聽來自伺服器端 Broadcasting Driver 的事件廣播。我們可以通過 NPM 套件管理員來安裝 Echo。在這個例子中,因為我們會使用 Pusher Channels Boradcaster,因此我們也會安裝 pusher-js

1npm install --save-dev laravel-echo pusher-js
1npm install --save-dev laravel-echo pusher-js

安裝好 Echo 後,就可以在網站的 JavaScript 中建立一個新的 Echo 實體。要建立新 Echo 實體最好的地方就是在 Laravel 附帶的 resources/js/bootstrap.js 檔案最尾端。預設情況下,這個檔案內已經包含了一個範例的 Echo 設定,只需要將其取消註解即可:

1import Echo from 'laravel-echo';
2import Pusher from 'pusher-js';
3 
4window.Pusher = Pusher;
5 
6window.Echo = new Echo({
7 broadcaster: 'pusher',
8 key: import.meta.env.VITE_PUSHER_APP_KEY,
9 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
10 forceTLS: true
11});
1import Echo from 'laravel-echo';
2import Pusher from 'pusher-js';
3 
4window.Pusher = Pusher;
5 
6window.Echo = new Echo({
7 broadcaster: 'pusher',
8 key: import.meta.env.VITE_PUSHER_APP_KEY,
9 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
10 forceTLS: true
11});

取消註解並依照需求調整好 Echo 設定後,就可以編譯專案素材:

1npm run dev
1npm run dev
lightbulb

要瞭解更多有關編譯應用程式 JavaScript 素材的資訊,請參考 Vite 中的說明文件。

使用現有的用戶端實體

若已經有預先設定好的 Pusher Channels 用戶端實體,並想讓 Echo 使用的話,可以將其傳入 Echo 的 client 設定選項:

1import Echo from 'laravel-echo';
2import Pusher from 'pusher-js';
3 
4const options = {
5 broadcaster: 'pusher',
6 key: 'your-pusher-channels-key'
7}
8 
9window.Echo = new Echo({
10 ...options,
11 client: new Pusher(options.key, options)
12});
1import Echo from 'laravel-echo';
2import Pusher from 'pusher-js';
3 
4const options = {
5 broadcaster: 'pusher',
6 key: 'your-pusher-channels-key'
7}
8 
9window.Echo = new Echo({
10 ...options,
11 client: new Pusher(options.key, options)
12});

Ably

lightbulb

下方的說明文件討論了如何在「Pusher 相容模式 (Pusher Compatibility)」下使用 Ably。不過,Ably 團隊推薦並維護了一個 Broadcaster 程式,以及一個可使用 Ably 特別功能的 Echo 用戶端。更多有關使用 Ably 維護的 Driver 的資訊,請參考 Ably 的 Laravel Broadcaster 說明文件 (英語)

Laravel Echo 是一個 JavaScript 套件,能讓你免於煩惱如何訂閱頻道與監聽來自伺服器端 Broadcasting Driver 的事件廣播。我們可以通過 NPM 套件管理員來安裝 Echo。在這個例子中,我們也會安裝 pusher-js

你可能會很困惑,為什麼我們明明是要用 Ably 來廣播事件,卻安裝了 pusher-js JavaScript 函式庫。謝天謝地,Ably 有個 Pusher 相容模式,可以讓我們在用戶端程式內監聽事件的時候使用 Pusher 協定:

1npm install --save-dev laravel-echo pusher-js
1npm install --save-dev laravel-echo pusher-js

在繼續之前,應先在 Ably 應用程式設定中啟用 Pusher 通訊協定。可以在 Ably 應用程式設定面板中的「Protocol Adapter Settings」這個部分內啟用此功能。

安裝好 Echo 後,就可以在網站的 JavaScript 中建立一個新的 Echo 實體。要建立新 Echo 實體最好的地方就是在 Laravel 附帶的 resources/js/bootstrap.js 檔案最尾端。預設情況下,這個檔案內已經包含了一個範例的 Echo 設定。不過,bootstrap.js 檔案中預設的範例是給 Pusher 用的。可以複製下列設定來將你的設定檔改成使用 Ably:

1import Echo from 'laravel-echo';
2import Pusher from 'pusher-js';
3 
4window.Pusher = Pusher;
5 
6window.Echo = new Echo({
7 broadcaster: 'pusher',
8 key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
9 wsHost: 'realtime-pusher.ably.io',
10 wsPort: 443,
11 disableStats: true,
12 encrypted: true,
13});
1import Echo from 'laravel-echo';
2import Pusher from 'pusher-js';
3 
4window.Pusher = Pusher;
5 
6window.Echo = new Echo({
7 broadcaster: 'pusher',
8 key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
9 wsHost: 'realtime-pusher.ably.io',
10 wsPort: 443,
11 disableStats: true,
12 encrypted: true,
13});

請注意,Ably Echo 設定中參考了 VITE_ABLY_PUBLIC_KEY 環境變數。這個環境變數應為 Ably 的公開金鑰。公開金鑰就是 Ably 金鑰中出現在 : 字元之前的部分。

取消註解並依照需求調整好 Echo 設定後,就可以編譯專案素材:

1npm run dev
1npm run dev
lightbulb

要瞭解更多有關編譯應用程式 JavaScript 素材的資訊,請參考 Vite 中的說明文件。

概念概覽

Laravel 的事件廣播功能能讓你以基於 Driver 的方法來將伺服器端的 Laravel 事件通過 WebSockets 廣播到用戶端 JavaScript 上。目前,Laravel 隨附了 Pusher ChannelsAbly 兩個 Driver。可以在用戶端使用 Laravel Echo JavaScript 套件來輕鬆取得事件。

事件是通過「頻道 (Channel)」進行廣播的,頻道可以被設為公共或私有。任何網站的瀏覽者都可以在不登入或經過授權的情況下訂閱公開頻道。不過,如果要訂閱私有頻道,就必須要登入並經過授權才可以監聽該頻道。

lightbulb

若想瞭解更多 Pusher 的開放原始碼替代品,請參考開放原始碼替代一節。

使用範例專案

在深入探討事件廣播的各個元件之前,我們先來用網路商店當作例子,以高階的角度來個概覽。

在我們的專案中,先來假設有個能讓使用者檢視訂單配送狀態的頁面。另外,也假設當網站處理到配送狀態更新的時候會觸發 OrderShipmentStatusUpdated 事件:

1use App\Events\OrderShipmentStatusUpdated;
2 
3OrderShipmentStatusUpdated::dispatch($order);
1use App\Events\OrderShipmentStatusUpdated;
2 
3OrderShipmentStatusUpdated::dispatch($order);

ShouldBroadcast 介面

我們並不希望使用者在檢視某個訂單的時候還需要重新整理整個頁面才能看到狀態更新;我們希望在訂單更新建立的時候就能廣播給專案。因此,我們需要將 OrderShipmentStatusUpdated 事件標上 ShouldBroadcast 介面。通過加上該介面,就能告訴 Laravel 要在該事件被觸發時將其廣播出去:

1<?php
2 
3namespace App\Events;
4 
5use App\Models\Order;
6use Illuminate\Broadcasting\Channel;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Broadcasting\PresenceChannel;
9use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10use Illuminate\Queue\SerializesModels;
11 
12class OrderShipmentStatusUpdated implements ShouldBroadcast
13{
14 /**
15 * The order instance.
16 *
17 * @var \App\Order
18 */
19 public $order;
20}
1<?php
2 
3namespace App\Events;
4 
5use App\Models\Order;
6use Illuminate\Broadcasting\Channel;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Broadcasting\PresenceChannel;
9use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10use Illuminate\Queue\SerializesModels;
11 
12class OrderShipmentStatusUpdated implements ShouldBroadcast
13{
14 /**
15 * The order instance.
16 *
17 * @var \App\Order
18 */
19 public $order;
20}

ShouldBroadcast 介面需要我們在事件中定義一個 broadcastOn 方法。這個方法需要回傳該事件廣播的頻道。產生的事件類別當中已經棒我們加上了一個空白的 Stub,因此我們只需要填寫詳情就好了。我們只希望建立該訂單的使用者檢視狀態更新,因此我們會將事件放在該訂單的私有頻道上廣播:

1use Illuminate\Broadcasting\Channel;
2use Illuminate\Broadcasting\PrivateChannel;
3 
4/**
5 * Get the channel the event should broadcast on.
6 */
7public function broadcastOn(): Channel
8{
9 return new PrivateChannel('orders.'.$this->order->id);
10}
1use Illuminate\Broadcasting\Channel;
2use Illuminate\Broadcasting\PrivateChannel;
3 
4/**
5 * Get the channel the event should broadcast on.
6 */
7public function broadcastOn(): Channel
8{
9 return new PrivateChannel('orders.'.$this->order->id);
10}

若想要讓 Event 被 Broadcast 到多個 Channel,可以回傳一組 array

1use Illuminate\Broadcasting\PrivateChannel;
2 
3/**
4 * Get the channels the event should broadcast on.
5 *
6 * @return array<int, \Illuminate\Broadcasting\Channel>
7 */
8public function broadcastOn(): array
9{
10 return [
11 new PrivateChannel('orders.'.$this->order->id),
12 // ...
13 ];
14}
1use Illuminate\Broadcasting\PrivateChannel;
2 
3/**
4 * Get the channels the event should broadcast on.
5 *
6 * @return array<int, \Illuminate\Broadcasting\Channel>
7 */
8public function broadcastOn(): array
9{
10 return [
11 new PrivateChannel('orders.'.$this->order->id),
12 // ...
13 ];
14}

授權頻道

請記得,使用者必須要經過授權才能監聽私有頻道。我們可以在 routes/channels.php 檔中定義頻道權限規則。在此例子中,我們需要認證嘗試監聽私有頻道 orders.1 的使用者是否為該訂單實際的建立人:

1use App\Models\Order;
2use App\Models\User;
3 
4Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
5 return $user->id === Order::findOrNew($orderId)->user_id;
6});
1use App\Models\Order;
2use App\Models\User;
3 
4Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
5 return $user->id === Order::findOrNew($orderId)->user_id;
6});

channel 方法接收 2 個引數:頻道的名稱,以及會回傳 truefalse 的回呼。這個回呼用來判斷使用者是否已授權監聽此頻道。

所有的授權回呼都會收到目前登入使用者作為其第一個引數,而接下來的引數則是其他額外的萬用字元參數。在這個例子中,我們使用了 {orderId} 預留位置來標示頻道名稱中的「ID」部分是萬用字元。

監聽事件廣播

接著,剩下的工作就是在 JavaScript 程式碼內監聽事件了。我們可以使用 Laravel Echo。首先,我們要先用 private 方法來監聽私有頻道。接著,可以監聽「listenOrderShipmentStatusUpdated 事件。預設情況下,該事件的所有公共屬性都會被包含在廣播事件內:

1Echo.private(`orders.${orderId}`)
2 .listen('OrderShipmentStatusUpdated', (e) => {
3 console.log(e.order);
4 });
1Echo.private(`orders.${orderId}`)
2 .listen('OrderShipmentStatusUpdated', (e) => {
3 console.log(e.order);
4 });

定義廣播事件

為了告訴 Laravel 應廣播某個給定的事件,我們必須要在事件類別上實作 Illuminate\Contracts\Broadcasting\ShouldBroadcast 介面。在所有產生出來的事件類別上,框架已經幫你引入這個介面了,因此你可以輕鬆地將該介面加至任何事件上。

ShouldBroadcast 介面只要求實作單一方法:broadcastOnbroadcastOn 方法應回傳一個頻道,或是一個包含頻道的陣列。這些頻道是事件要進行廣播的頻道。頻道應為 Channel, PrivateChannelPresenceChannel 的實體。Channel 的實體代表任何使用者都能監聽的公共頻道,而 PrivateChannelPresenceChannels 代表需要進行頻道授權的私有頻道:

1<?php
2 
3namespace App\Events;
4 
5use App\Models\User;
6use Illuminate\Broadcasting\Channel;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Broadcasting\PresenceChannel;
9use Illuminate\Broadcasting\PrivateChannel;
10use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
11use Illuminate\Queue\SerializesModels;
12 
13class ServerCreated implements ShouldBroadcast
14{
15 use SerializesModels;
16 
17 /**
18 * Create a new event instance.
19 */
20 public function __construct(
21 public User $user,
22 ) {}
23 
24 /**
25 * Get the channels the event should broadcast on.
26 *
27 * @return array<int, \Illuminate\Broadcasting\Channel>
28 */
29 public function broadcastOn(): array
30 {
31 return [
32 new PrivateChannel('user.'.$this->user->id),
33 ];
34 }
35}
1<?php
2 
3namespace App\Events;
4 
5use App\Models\User;
6use Illuminate\Broadcasting\Channel;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Broadcasting\PresenceChannel;
9use Illuminate\Broadcasting\PrivateChannel;
10use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
11use Illuminate\Queue\SerializesModels;
12 
13class ServerCreated implements ShouldBroadcast
14{
15 use SerializesModels;
16 
17 /**
18 * Create a new event instance.
19 */
20 public function __construct(
21 public User $user,
22 ) {}
23 
24 /**
25 * Get the channels the event should broadcast on.
26 *
27 * @return array<int, \Illuminate\Broadcasting\Channel>
28 */
29 public function broadcastOn(): array
30 {
31 return [
32 new PrivateChannel('user.'.$this->user->id),
33 ];
34 }
35}

實作完 ShouldBroadcast 介面後,只需要像平常一樣觸發事件即可。事件被觸發後,佇列任務會自動通過指定的 Broadcast Driver 來廣播事件。

Broadcast 名稱

預設情況下,Laravel 會使用事件的類別名來進行廣播。不過,也可以在事件上定義 broadcastAs 方法來自訂 Broadcast 名稱:

1/**
2 * The event's broadcast name.
3 */
4public function broadcastAs(): string
5{
6 return 'server.created';
7}
1/**
2 * The event's broadcast name.
3 */
4public function broadcastAs(): string
5{
6 return 'server.created';
7}

若使用 broadcastAs 方法來自訂 Broadcast 名稱,則應確保註冊監聽程式時有加上前置 . 字元。加上該前置字元可用來告訴 Echo 不要在事件前方加上專案的命名空間:

1.listen('.server.created', function (e) {
2 ....
3});
1.listen('.server.created', function (e) {
2 ....
3});

Broadcast 資料

廣播事件時,事件所有的 public 屬性都會被自動序列化,並作為事件的 Payload 進行廣播,讓你能在 JavaScript 程式碼中存取事件的所有公共資料。因此,舉例來說,假設我們的事件有一個 public $user 屬性,其中包含了 Eloquent Model,那麼事件的 Broadcast Payload 會是:

1{
2 "user": {
3 "id": 1,
4 "name": "Patrick Stewart"
5 ...
6 }
7}
1{
2 "user": {
3 "id": 1,
4 "name": "Patrick Stewart"
5 ...
6 }
7}

不過,若想對 Broadcast Payload 進一步地控制,可以在事件內加上一個 broadcastWith 方法。這個方法應回傳一個陣列,包含要作為事件 Payload 使用的資料:

1/**
2 * Get the data to broadcast.
3 *
4 * @return array<string, mixed>
5 */
6public function broadcastWith(): array
7{
8 return ['id' => $this->user->id];
9}
1/**
2 * Get the data to broadcast.
3 *
4 * @return array<string, mixed>
5 */
6public function broadcastWith(): array
7{
8 return ['id' => $this->user->id];
9}

Broadcast 佇列

預設情況下,所有的廣播事件都會使用 queue.php 設定檔中的預設佇列連連。可以通過在事件類別內定義 queue 屬性來自訂 Broadcaster 要使用的佇列連線名稱:

1/**
2 * The name of the queue connection to use when broadcasting the event.
3 *
4 * @var string
5 */
6public $connection = 'redis';
7 
8/**
9 * The name of the queue on which to place the broadcasting job.
10 *
11 * @var string
12 */
13public $queue = 'default';
1/**
2 * The name of the queue connection to use when broadcasting the event.
3 *
4 * @var string
5 */
6public $connection = 'redis';
7 
8/**
9 * The name of the queue on which to place the broadcasting job.
10 *
11 * @var string
12 */
13public $queue = 'default';

或者,你也可以通過在事件中定義 broadcastQueue 方法來自訂佇列名稱:

1/**
2 * The name of the queue on which to place the broadcasting job.
3 */
4public function broadcastQueue(): string
5{
6 return 'default';
7}
1/**
2 * The name of the queue on which to place the broadcasting job.
3 */
4public function broadcastQueue(): string
5{
6 return 'default';
7}

若像使用 sync 佇列來代替預設的佇列 Driver,可以使用 ShouldBroadcastNow 來代替 ShouldBroadcast 進行實作:

1<?php
2 
3use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
4 
5class OrderShipmentStatusUpdated implements ShouldBroadcastNow
6{
7 // ...
8}
1<?php
2 
3use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
4 
5class OrderShipmentStatusUpdated implements ShouldBroadcastNow
6{
7 // ...
8}

Broadcast 條件

有時候我們可能只想在滿足給定條件的時候才廣播事件。可以通過在事件類別上新增 broadcastWhen 方法來在其中定義這些條件:

1/**
2 * Determine if this event should broadcast.
3 */
4public function broadcastWhen(): bool
5{
6 return $this->order->value > 100;
7}
1/**
2 * Determine if this event should broadcast.
3 */
4public function broadcastWhen(): bool
5{
6 return $this->order->value > 100;
7}

Broadcast 與資料庫 Transaction

當廣播事件是在資料庫 Transaction 內分派的時候,這個事件可能會在資料庫 Transaction 被 Commit 前被佇列進行處理了。發生這種情況時,在資料庫 Transaction 期間對 Model 或資料庫記錄所做出的更新可能都還未反應到資料庫內。另外,所有在 Transaction 期間新增的 Model 或資料庫記錄也可能還未出現在資料庫內。若事件有依賴這些 Model 的話,在處理廣播事件的任務時可能會出現未預期的錯誤。

即使佇列連線的 after_commit 設定選項被設為 false,還是可以通過在 Event 類別上實作 ShouldDispatchAfterCommit 介面來讓 Laravel 知道要在所有開啟的資料庫 Transaction 被 Commit 後廣播該 Event:

1<?php
2 
3namespace App\Events;
4 
5use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
6use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
7use Illuminate\Queue\SerializesModels;
8 
9class ServerCreated implements ShouldBroadcast, ShouldDispatchAfterCommit
10{
11 use SerializesModels;
12}
1<?php
2 
3namespace App\Events;
4 
5use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
6use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
7use Illuminate\Queue\SerializesModels;
8 
9class ServerCreated implements ShouldBroadcast, ShouldDispatchAfterCommit
10{
11 use SerializesModels;
12}
lightbulb

要瞭解更多有關這類問題的解決方法,請參考有關佇列任務與資料庫 Transaction 有關的說明文件。

授權頻道

使用私有頻道,則需要將目前已登入的使用者授權為可監聽該頻道。要授權使用者,需要向 Laravel 端傳送一個包含頻道名稱的 HTTP 請求來讓網站判斷使用者能否監聽該頻道。使用 Laravel Echo 時,會自動建立用於授權訂閱私有頻道的 HTTP 請求。不過,我們還是需要定義適當的路由來回應這些請求。

定義授權路由

好佳在,在 Laravel 中定義回應頻道授權請求的路由非常容易。在 Laravel 中隨附的 App\Providers\BroadcastServiceProvider 內,可以看到一個 Broadcast::routes 方法的呼叫。這個方法會註冊 /broadcasting/auth 路由來處理授權請求:

1Broadcast::routes();
1Broadcast::routes();

Broadcast::routes 方法會自動將其中的路由放置於 web Middleware 群組內。不過,若想自訂指派的屬性,也可以傳入包含路由屬性的陣列:

1Broadcast::routes($attributes);
1Broadcast::routes($attributes);

自訂授權 Endpoint

預設情況下,Echo 會使用 /broadcasting/auth Endpoint 來授權頻道存取。不過,也可以通過將 authEndpoint 設定選項傳給 Echo 實體來指定你自己的授權 Endpoint:

1window.Echo = new Echo({
2 broadcaster: 'pusher',
3 // ...
4 authEndpoint: '/custom/endpoint/auth'
5});
1window.Echo = new Echo({
2 broadcaster: 'pusher',
3 // ...
4 authEndpoint: '/custom/endpoint/auth'
5});

自訂授權 Request

我們可以自訂 Laravel Echo 要如何執行授權請求。只需要在初始化 Echo 時提供一個自訂授權程式即可:

1window.Echo = new Echo({
2 // ...
3 authorizer: (channel, options) => {
4 return {
5 authorize: (socketId, callback) => {
6 axios.post('/api/broadcasting/auth', {
7 socket_id: socketId,
8 channel_name: channel.name
9 })
10 .then(response => {
11 callback(null, response.data);
12 })
13 .catch(error => {
14 callback(error);
15 });
16 }
17 };
18 },
19})
1window.Echo = new Echo({
2 // ...
3 authorizer: (channel, options) => {
4 return {
5 authorize: (socketId, callback) => {
6 axios.post('/api/broadcasting/auth', {
7 socket_id: socketId,
8 channel_name: channel.name
9 })
10 .then(response => {
11 callback(null, response.data);
12 })
13 .catch(error => {
14 callback(error);
15 });
16 }
17 };
18 },
19})

定義授權回呼

接著,我們需要定義實際上用來判斷目前登入使用者是否能監聽給定頻道的邏輯。這個定義放在專案內routes/channels.php 檔案中。在這個檔案中,可以使用 Broadcast::channel 方法來註冊頻道授權回呼:

1use App\Models\User;
2 
3Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
4 return $user->id === Order::findOrNew($orderId)->user_id;
5});
1use App\Models\User;
2 
3Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
4 return $user->id === Order::findOrNew($orderId)->user_id;
5});

channel 方法接收 2 個引數:頻道的名稱,以及會回傳 truefalse 的回呼。這個回呼用來判斷使用者是否已授權監聽此頻道。

所有的授權回呼都會收到目前登入使用者作為其第一個引數,而接下來的引數則是其他額外的萬用字元參數。在這個例子中,我們使用了 {orderId} 預留位置來標示頻道名稱中的「ID」部分是萬用字元。

可以使用 channel:list Artisan 指令來檢視專案中所有 Broadcast 身分驗證回呼的列表:

1php artisan channel:list
1php artisan channel:list

授權回呼的 Model

就像 HTTP 路由一樣,頻道路由也能使用顯式或隱式路由 Model 的功能。舉例來說,可以不接收字串或數字的 Order ID,而要求實際的 Order Model 實體:

1use App\Models\Order;
2use App\Models\User;
3 
4Broadcast::channel('orders.{order}', function (User $user, Order $order) {
5 return $user->id === $order->user_id;
6});
1use App\Models\Order;
2use App\Models\User;
3 
4Broadcast::channel('orders.{order}', function (User $user, Order $order) {
5 return $user->id === $order->user_id;
6});
exclamation

與 HTTP 路由 Model 綁定不同,頻道的 Model 綁定不支援自動[為隱式 Model 綁定加上作用域]。不過,通常來說這不會造成問題,因為大部分的頻道都可以被放置與單一 Model 的獨立主鍵作用域內。

授權回呼認證

私有與 Presence 廣播頻道會通過專案預設的認證 Guard 來認證目前的使用者。若使用者未登入,則頻道認證會自動拒絕,且授權回呼永遠不會被執行。不過,若有需要,也可以指定多個自訂 Guard 來認證連入請求:

1Broadcast::channel('channel', function () {
2 // ...
3}, ['guards' => ['web', 'admin']]);
1Broadcast::channel('channel', function () {
2 // ...
3}, ['guards' => ['web', 'admin']]);

定義 Channel 類別

若你的專案會使用到許多不同的頻道,則 routes/channels.php 可能會變得很肥大。因此,比起使用閉包來授權頻道,我們可以改用頻道類別。要建立頻道類別,請使用 make:channel Artisan 指令。這個指令會在 app/Broadcasting 目錄內放置一個新的頻道類別。

1php artisan make:channel OrderChannel
1php artisan make:channel OrderChannel

接著,在 routes/channels.php 檔案內註冊頻道:

1use App\Broadcasting\OrderChannel;
2 
3Broadcast::channel('orders.{order}', OrderChannel::class);
1use App\Broadcasting\OrderChannel;
2 
3Broadcast::channel('orders.{order}', OrderChannel::class);

最後,可以將頻道的授權邏輯放在頻道類別的 join 方法內。這個 join 方法用來放置與平常放在頻道授權閉包相同的邏輯。也可以使用頻道 Model 綁定:

1<?php
2 
3namespace App\Broadcasting;
4 
5use App\Models\Order;
6use App\Models\User;
7 
8class OrderChannel
9{
10 /**
11 * Create a new channel instance.
12 */
13 public function __construct()
14 {
15 // ...
16 }
17 
18 /**
19 * Authenticate the user's access to the channel.
20 */
21 public function join(User $user, Order $order): array|bool
22 {
23 return $user->id === $order->user_id;
24 }
25}
1<?php
2 
3namespace App\Broadcasting;
4 
5use App\Models\Order;
6use App\Models\User;
7 
8class OrderChannel
9{
10 /**
11 * Create a new channel instance.
12 */
13 public function __construct()
14 {
15 // ...
16 }
17 
18 /**
19 * Authenticate the user's access to the channel.
20 */
21 public function join(User $user, Order $order): array|bool
22 {
23 return $user->id === $order->user_id;
24 }
25}
lightbulb

與 Laravel 內其他類別一樣,頻道類別也會自動由 Service Container 解析。因此,我們可以在頻道的建構函式上對任何所需要的依賴進行型別提示。

廣播事件

定義好事件並將其以 ShouldBroadcast 介面進行標示後,我們只需要使用 dispatch 方法來觸發事件即可。事件觸發程式會注意到這個事件有被標註為 ShouldBroadcast 介面,並將事件放入佇列以進行廣播:

1use App\Events\OrderShipmentStatusUpdated;
2 
3OrderShipmentStatusUpdated::dispatch($order);
1use App\Events\OrderShipmentStatusUpdated;
2 
3OrderShipmentStatusUpdated::dispatch($order);

僅限其他

在建立一個有使用到事件廣播的專案時,我們可能會需要將某個事件廣播給除了目前使用者以外的所有頻道訂閱者。可以通過 broadcast 輔助函式以及 toOthers 方法來完成:

1use App\Events\OrderShipmentStatusUpdated;
2 
3broadcast(new OrderShipmentStatusUpdated($update))->toOthers();
1use App\Events\OrderShipmentStatusUpdated;
2 
3broadcast(new OrderShipmentStatusUpdated($update))->toOthers();

為了幫助你更容易理解什麼時候會需要用到 toOthers 方法,我們來假設有個任務清單 App。在這個 App 中,使用者可以輸入任務名稱來新增任務。為了建立任務,這個 App 可能會向 /task URL 發起一個請求,該請求會將任務的建立廣播出去,並回傳代表新任務的 JSON。當 JavaScript 端從這個 End-point 收到回覆後,就可以直接將新任務插入到任務清單內。像這樣:

1axios.post('/task', task)
2 .then((response) => {
3 this.tasks.push(response.data);
4 });
1axios.post('/task', task)
2 .then((response) => {
3 this.tasks.push(response.data);
4 });

不過,提醒一下,我們也會將任務的建立廣播出去。如果 JavaScript 端也會監聽這個事件來將任務新增到任務清單上,那麼列表上就會有重複的任務:一個是從 End-point 回傳回來的,另一個則是從監聽事件來的。我們可以通過使用 toOthers 方法來告訴廣播程式不要將該事件廣播給目前的使用者。

exclamation

若要呼叫 toOthers 方法,該事件必須要 use Illuminate\Broadcasting\InteractsWithSockets Trait。

設定

初始化 Laravel Echo 實體時,會指派一個 Socket ID 給這個連線。若是使用全域的 Axios 實體來在 JavaScript 端建立 HTTP 連線,則 Socket ID 會以 X-Socket-ID 標頭被自動附加到每個外連請求上。接著,當呼叫 toOthers 方法時,Laravel 會從標頭內拆出這個 Socket ID,並告知廣播程式不要廣播給有該 Socket ID 的連線。

若你未使用全域 Axios 實體,則需要手動設定 JavaScript 端來在所有外連請求上傳送 X-Socket-ID 標頭。可以通過 Echo.socketId 方法來取得 Socket ID:

1var socketId = Echo.socketId();
1var socketId = Echo.socketId();

自訂連線

若你的專案與許多不同的廣播連線互動時,如果我們想使用預設廣播程式以外的特定廣播程式來廣播事件,則可以使用 via 方法來指定要將事件推送給哪個連線:

1use App\Events\OrderShipmentStatusUpdated;
2 
3broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');
1use App\Events\OrderShipmentStatusUpdated;
2 
3broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');

或者,也可以通過在事件的建構函式 (Constructor) 內呼叫 broadcastVia 方法來指定事件的廣播連線。不過,這麼做的時候,請先確保這個事件類別有使用 InteractsWithBroadcasting Trait:

1<?php
2 
3namespace App\Events;
4 
5use Illuminate\Broadcasting\Channel;
6use Illuminate\Broadcasting\InteractsWithBroadcasting;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Broadcasting\PresenceChannel;
9use Illuminate\Broadcasting\PrivateChannel;
10use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
11use Illuminate\Queue\SerializesModels;
12 
13class OrderShipmentStatusUpdated implements ShouldBroadcast
14{
15 use InteractsWithBroadcasting;
16 
17 /**
18 * Create a new event instance.
19 */
20 public function __construct()
21 {
22 $this->broadcastVia('pusher');
23 }
24}
1<?php
2 
3namespace App\Events;
4 
5use Illuminate\Broadcasting\Channel;
6use Illuminate\Broadcasting\InteractsWithBroadcasting;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Broadcasting\PresenceChannel;
9use Illuminate\Broadcasting\PrivateChannel;
10use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
11use Illuminate\Queue\SerializesModels;
12 
13class OrderShipmentStatusUpdated implements ShouldBroadcast
14{
15 use InteractsWithBroadcasting;
16 
17 /**
18 * Create a new event instance.
19 */
20 public function __construct()
21 {
22 $this->broadcastVia('pusher');
23 }
24}

接收廣播

監聽事件

安裝並設定好 Laravel Echo 後,就可以開始監聽來自 Laravel 端廣播的事件了。首先,使用 channel 方法來取得頻道的實體,然後呼叫 listen 方法來監聽某個特定的事件:

1Echo.channel(`orders.${this.order.id}`)
2 .listen('OrderShipmentStatusUpdated', (e) => {
3 console.log(e.order.name);
4 });
1Echo.channel(`orders.${this.order.id}`)
2 .listen('OrderShipmentStatusUpdated', (e) => {
3 console.log(e.order.name);
4 });

若想監聽私有頻道,可使用 private 方法來代替。可以繼續在 listen 方法後方串上其他的呼叫來在單一頻道上監聽多個事件:

1Echo.private(`orders.${this.order.id}`)
2 .listen(/* ... */)
3 .listen(/* ... */)
4 .listen(/* ... */);
1Echo.private(`orders.${this.order.id}`)
2 .listen(/* ... */)
3 .listen(/* ... */)
4 .listen(/* ... */);

停止監聽事件

若想在不離開頻道的情況下停止監聽給定的事件,可以使用 stopListening 方法:

1Echo.private(`orders.${this.order.id}`)
2 .stopListening('OrderShipmentStatusUpdated')
1Echo.private(`orders.${this.order.id}`)
2 .stopListening('OrderShipmentStatusUpdated')

離開頻道

若要離開頻道,可以在 Echo 實體上呼叫 leaveChannel 方法:

1Echo.leaveChannel(`orders.${this.order.id}`);
1Echo.leaveChannel(`orders.${this.order.id}`);

若要離開頻道以及其關聯的私有與 Presence 頻道,可以呼叫 leave 方法:

1Echo.leave(`orders.${this.order.id}`);
1Echo.leave(`orders.${this.order.id}`);

命名空間 (Namespace)

你可能已經注意到,我們並沒有為事件類別指定完整的 App\Events 命名空間。這是因為,Echo 會自動假設事件都放在 App\Events 命名空間下。不過,我們可以在初始化 Echo 時傳入 namespace 設定選項來設定要使用的根命名空間:

1window.Echo = new Echo({
2 broadcaster: 'pusher',
3 // ...
4 namespace: 'App.Other.Namespace'
5});
1window.Echo = new Echo({
2 broadcaster: 'pusher',
3 // ...
4 namespace: 'App.Other.Namespace'
5});

除了在初始化時設定以外,也可以在使用 Echo 訂閱事件時在事件類別的名稱前加上一個前置 .。這樣一來,就可以隨時使用完整的類別名稱:

1Echo.channel('orders')
2 .listen('.Namespace\\Event\\Class', (e) => {
3 // ...
4 });
1Echo.channel('orders')
2 .listen('.Namespace\\Event\\Class', (e) => {
3 // ...
4 });

Presence 頻道

Presence 頻道擁有私有頻道的安全性,且會提供該頻道的訂閱者等額外資訊。這樣一來便能輕鬆地建立強大的協作 App 功能,如提示目前使用者由其他人正在檢視相同頁面,或是列出聊天室中的使用者狀態。

授權 Presence 頻道

所有的 Presence 頻道也同時是私有頻道。因此,使用者必須要經過授權以存取頻道。不過,在為 Presence 頻道定義授權回呼時,若要授權使用者加入頻道,則不應回傳 true,而應回傳包含有關其他使用者資訊的陣列。

由授權回呼回傳的資料可以在 JavaScript 端中的 Presence 頻道事件監聽程式中使用。若使用者未被授權加入 Presence 頻道,則應回傳 falsenull

1use App\Models\User;
2 
3Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
4 if ($user->canJoinRoom($roomId)) {
5 return ['id' => $user->id, 'name' => $user->name];
6 }
7});
1use App\Models\User;
2 
3Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
4 if ($user->canJoinRoom($roomId)) {
5 return ['id' => $user->id, 'name' => $user->name];
6 }
7});

加入 Presence 頻道

若要加入 Presence 頻道,可以使用 Echo 的 join 方法。join 方法會與所暴露的 listen 方法一起回傳一個 PresenceChannel 的實作,這樣一來你就能訂閱 here, joining 以及 leaving 事件。

1Echo.join(`chat.${roomId}`)
2 .here((users) => {
3 // ...
4 })
5 .joining((user) => {
6 console.log(user.name);
7 })
8 .leaving((user) => {
9 console.log(user.name);
10 })
11 .error((error) => {
12 console.error(error);
13 });
1Echo.join(`chat.${roomId}`)
2 .here((users) => {
3 // ...
4 })
5 .joining((user) => {
6 console.log(user.name);
7 })
8 .leaving((user) => {
9 console.log(user.name);
10 })
11 .error((error) => {
12 console.error(error);
13 });

here 回呼會在成功加入頻道後被立即執行,並會收到包含所有其他目前訂閱該頻道的使用者資訊。joining 方法會在有新使用者加入頻道時被執行,而 leaving 則會在有使用者離開時被執行。error 方法會在認證 Endpoint 回傳除了 200 以外的 HTTP 狀態時、或是解析回傳的 JSON 時有問題時被執行。

廣播至 Presence 頻道

Presence 頻道可以像公用或私有頻道一樣接收事件。以聊天室為例,我們可能像廣播 NewMessage 事件至聊天室的 Presence 頻道。為此,我們可以在事件的 broadcastOn 方法內回傳一個 PresenceChannel 的實體:

1/**
2 * Get the channels the event should broadcast on.
3 *
4 * @return array<int, \Illuminate\Broadcasting\Channel>
5 */
6public function broadcastOn(): array
7{
8 return [
9 new PresenceChannel('chat.'.$this->message->room_id),
10 ];
11}
1/**
2 * Get the channels the event should broadcast on.
3 *
4 * @return array<int, \Illuminate\Broadcasting\Channel>
5 */
6public function broadcastOn(): array
7{
8 return [
9 new PresenceChannel('chat.'.$this->message->room_id),
10 ];
11}

與其他事件一樣,可以使用 broadcast 輔助函式與 toOthers 方法來排除目前使用者接收該 Broadcast:

1broadcast(new NewMessage($message));
2 
3broadcast(new NewMessage($message))->toOthers();
1broadcast(new NewMessage($message));
2 
3broadcast(new NewMessage($message))->toOthers();

與其他一般的事件一樣,也可以使用 Echo 的 listen 方法來監聽傳送到 Presence 頻道的事件:

1Echo.join(`chat.${roomId}`)
2 .here(/* ... */)
3 .joining(/* ... */)
4 .leaving(/* ... */)
5 .listen('NewMessage', (e) => {
6 // ...
7 });
1Echo.join(`chat.${roomId}`)
2 .here(/* ... */)
3 .joining(/* ... */)
4 .leaving(/* ... */)
5 .listen('NewMessage', (e) => {
6 // ...
7 });

Model 廣播

exclamation

在進一步閱讀有關 Model 廣播的說明文件前,我們建議讀者先瞭解有關 Laravel 的 Model 廣播服務以及如何手動建立並監聽廣播時間的一般概念。

在專案的 Eloquent Model 被建立、更新、或刪除時,我們常常會廣播事件。當然,我們可以手動為 Eloquent Model 的狀態更改定義自訂事件並將這些事件標記為 ShouldBroadcast 來輕鬆達成:

不過,我們讓事件在專案中負責其他功能,那麼如果只建立一個用來廣播的事件就很麻煩。為了解決這個問題,再 Laravel 中,我們可以讓 Eloquent Model 自動將其狀態更改廣播出去:

要開始設定自動廣播,應在 Eloquent Model 上使用 Illuminate\Database\Eloquent\BroadcastsEvents Trait。此外,該 Model 應定義一個 broadcastOn 方法,並在其中回傳一組包含頻道名稱的陣列,以供 Model 事件廣播:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Broadcasting\Channel;
6use Illuminate\Broadcasting\PrivateChannel;
7use Illuminate\Database\Eloquent\BroadcastsEvents;
8use Illuminate\Database\Eloquent\Factories\HasFactory;
9use Illuminate\Database\Eloquent\Model;
10use Illuminate\Database\Eloquent\Relations\BelongsTo;
11 
12class Post extends Model
13{
14 use BroadcastsEvents, HasFactory;
15 
16 /**
17 * Get the user that the post belongs to.
18 */
19 public function user(): BelongsTo
20 {
21 return $this->belongsTo(User::class);
22 }
23 
24 /**
25 * Get the channels that model events should broadcast on.
26 *
27 * @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
28 */
29 public function broadcastOn(string $event): array
30 {
31 return [$this, $this->user];
32 }
33}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Broadcasting\Channel;
6use Illuminate\Broadcasting\PrivateChannel;
7use Illuminate\Database\Eloquent\BroadcastsEvents;
8use Illuminate\Database\Eloquent\Factories\HasFactory;
9use Illuminate\Database\Eloquent\Model;
10use Illuminate\Database\Eloquent\Relations\BelongsTo;
11 
12class Post extends Model
13{
14 use BroadcastsEvents, HasFactory;
15 
16 /**
17 * Get the user that the post belongs to.
18 */
19 public function user(): BelongsTo
20 {
21 return $this->belongsTo(User::class);
22 }
23 
24 /**
25 * Get the channels that model events should broadcast on.
26 *
27 * @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
28 */
29 public function broadcastOn(string $event): array
30 {
31 return [$this, $this->user];
32 }
33}

再 Model 中包含該 Trait 並定義好廣播頻道後,當 Model 實體被建立、更新、刪除、軟刪除、或是取消軟刪除後自動廣播事件。

此外,讀者可能已經發現,broadcastOn 方法接收了一個字串的 $event 引述。這個引述包含了 Model 上所發生的事件,其值為 created, updated, deleted, trashed, 或 restored。只要檢查這個變數的值,就可以用來判斷對於特定事件要廣播道哪個頻道(若有的話):

1/**
2 * Get the channels that model events should broadcast on.
3 *
4 * @return array<string, array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>>
5 */
6public function broadcastOn(string $event): array
7{
8 return match ($event) {
9 'deleted' => [],
10 default => [$this, $this->user],
11 };
12}
1/**
2 * Get the channels that model events should broadcast on.
3 *
4 * @return array<string, array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>>
5 */
6public function broadcastOn(string $event): array
7{
8 return match ($event) {
9 'deleted' => [],
10 default => [$this, $this->user],
11 };
12}

自訂 Model 廣播的事件建立

有時候,我們可能會想自訂 Laravel 要如何建立 Model 廣播時使用的事件。為此,我們可以通過在 Eloquent Model 上定義一個 newBroadcastableEvent 來達成。該方法應回傳 Illuminate\Database\Eloquent\BroadcastableModelEventOccurred 實體:

1use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;
2 
3/**
4 * Create a new broadcastable model event for the model.
5 */
6protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
7{
8 return (new BroadcastableModelEventOccurred(
9 $this, $event
10 ))->dontBroadcastToCurrentUser();
11}
1use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;
2 
3/**
4 * Create a new broadcastable model event for the model.
5 */
6protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
7{
8 return (new BroadcastableModelEventOccurred(
9 $this, $event
10 ))->dontBroadcastToCurrentUser();
11}

Model 廣播慣例

頻道慣例

讀者可能已經發現,在上方的 Model 範例中,broadcastOn 方法並沒有回傳 Channel 實體,而是直接回傳 Eloquent Model。若 Model 的 broadcastOn 方法回傳的是 Model 實體 (或是包含 Model 實體的陣列),則 Laravel 會使用該 Model 的類別名稱與主索引鍵識別元作為頻道名稱,自動為該 Model 初始化一個私人頻道。

因此,id1App\Models\User Model 會被轉換為一個名稱是 App.Models.User.1Illuminate\Broadcasting\PrivateChannel 實體。當然,除了從 Model 的 broadcastOn 方法內回傳 Eloquent Model 實體外,也可以回傳一個完整的 Channel 實體來取得對 Model 的頻道名稱的完整控制權:

1use Illuminate\Broadcasting\PrivateChannel;
2 
3/**
4 * Get the channels that model events should broadcast on.
5 *
6 * @return array<int, \Illuminate\Broadcasting\Channel>
7 */
8public function broadcastOn(string $event): array
9{
10 return [
11 new PrivateChannel('user.'.$this->id)
12 ];
13}
1use Illuminate\Broadcasting\PrivateChannel;
2 
3/**
4 * Get the channels that model events should broadcast on.
5 *
6 * @return array<int, \Illuminate\Broadcasting\Channel>
7 */
8public function broadcastOn(string $event): array
9{
10 return [
11 new PrivateChannel('user.'.$this->id)
12 ];
13}

若有打算要從 Model 的 broadcastOn 方法內明顯回傳頻道實體,則可以將 Eloquent Model 實體傳入該頻道的建構函式。這樣一來,Laravel 就可以通過剛才提到的 Model 頻道慣例來將 Eloquent Model 轉換為頻道名稱字串:

1return [new Channel($this->user)];
1return [new Channel($this->user)];

若想判斷某個 Model 的頻道名稱,可以在任何 Model 實體上呼叫 broadcastChannel 方法。舉例來說,對於一個 id1App\Models\User Model,該方法會回傳一個字串 App.Models.User.1

1$user->broadcastChannel()
1$user->broadcastChannel()

事件慣例

由於 Model 廣播事件並不與專案的 App\Events 目錄內的「真實」事件有關,這些事件只會依據慣例來指派名稱與 Payload (裝載)。Laravel 的慣例就是使用 Model 的類別名稱 (不含 Namespace) 與觸發廣播的 Model 事件來廣播。

因此,對 App\Models\Post Model 進行更新,會將 PostUpdated 事件與下列 Payload 廣播到用戶端:

1{
2 "model": {
3 "id": 1,
4 "title": "My first post"
5 ...
6 },
7 ...
8 "socket": "someSocketId",
9}
1{
2 "model": {
3 "id": 1,
4 "title": "My first post"
5 ...
6 },
7 ...
8 "socket": "someSocketId",
9}

刪除 App\Models\Post Model 時廣播的事件名稱會是 UserDeleted

若有需要,也可以通過在 Model 中新增一個 broadcastAsbroadcastWith 方法來自訂廣播的名稱與 Payload。這些方法會收到目前發生的 Model 事件或動作,好讓我們能為不同的 Model 動作自訂事件名稱與 Payload。若在 broadcastAs 方法中回傳 null,則 Laravel 會使用上方討論過的 Model 廣播事件名稱的慣例來廣播這個事件:

1/**
2 * The model event's broadcast name.
3 */
4public function broadcastAs(string $event): string|null
5{
6 return match ($event) {
7 'created' => 'post.created',
8 default => null,
9 };
10}
11 
12/**
13 * Get the data to broadcast for the model.
14 *
15 * @return array<string, mixed>
16 */
17public function broadcastWith(string $event): array
18{
19 return match ($event) {
20 'created' => ['title' => $this->title],
21 default => ['model' => $this],
22 };
23}
1/**
2 * The model event's broadcast name.
3 */
4public function broadcastAs(string $event): string|null
5{
6 return match ($event) {
7 'created' => 'post.created',
8 default => null,
9 };
10}
11 
12/**
13 * Get the data to broadcast for the model.
14 *
15 * @return array<string, mixed>
16 */
17public function broadcastWith(string $event): array
18{
19 return match ($event) {
20 'created' => ['title' => $this->title],
21 default => ['model' => $this],
22 };
23}

監聽 Model 廣播

在 Model 中新增好 BroadcastsEvents Trait 並定義好 Model 的 broadcastOn 方法後,就可以開始在用戶端中監聽廣播出來的 Model 事件了。在開始前,建議你先閱讀有關監聽事件的完整說明文件。

首先,使用 private 方法來取得 Channel 實體,然後呼叫 listen 方法來監聽特定的事件。一般來說,傳給 private 方法的頻道名稱應與 Laravel 的 Model 廣播慣例相對應。

取得 Channel 實體後,就可以使用 listen 方法來監聽特定的事件。由於 Model 廣播事件並不與專案中 App\Events 目錄下的「真實事件」互相關聯,因此,事件名稱前應加上一個 . 字元,以標識其不屬於特定的命名空間。每個 Model 廣播事件都有一個 model 屬性,其中包含了 Model 中所有可廣播的屬性:

1Echo.private(`App.Models.User.${this.user.id}`)
2 .listen('.PostUpdated', (e) => {
3 console.log(e.model);
4 });
1Echo.private(`App.Models.User.${this.user.id}`)
2 .listen('.PostUpdated', (e) => {
3 console.log(e.model);
4 });

用戶端事件

lightbulb

在使用 Pusher Channels 時,可以在 Application Dashboard 內啟用「App Settings」中的「Client Event」,以傳送用戶端事件。

有時候我們可能會想將事件直接廣播給其他連線的用戶端,而不經由 Laravel 端。特別像是如顯示「正在輸入」等通知時,我們只是想告訴使用者網站內的其他使用者正在特定畫面上輸入。

若要廣播用戶端事件,可以使用 Echo 的 whisper 方法:

1Echo.private(`chat.${roomId}`)
2 .whisper('typing', {
3 name: this.user.name
4 });
1Echo.private(`chat.${roomId}`)
2 .whisper('typing', {
3 name: this.user.name
4 });

若要監聽用戶端事件,可以使用 listenForWhisper 方法:

1Echo.private(`chat.${roomId}`)
2 .listenForWhisper('typing', (e) => {
3 console.log(e.name);
4 });
1Echo.private(`chat.${roomId}`)
2 .listenForWhisper('typing', (e) => {
3 console.log(e.name);
4 });

通知

只要將事件廣播與 通知 一起使用,JavaScript 端就可以在不重新整理的情況下接收新通知。在開始前,請先閱讀有關廣播通知頻道的說明文件。

設定讓通知使用廣播頻道後,就可以使用 Echo 的 notification 方法來接收廣播。請記住,頻道的名稱應與接收通知的使用者類別名稱相符:

1Echo.private(`App.Models.User.${userId}`)
2 .notification((notification) => {
3 console.log(notification.type);
4 });
1Echo.private(`App.Models.User.${userId}`)
2 .notification((notification) => {
3 console.log(notification.type);
4 });

在此範例中,所有通過 broadcast 頻道傳送給 App\Models\User 實體的通知都會被該回呼收到。用於 App.Models.User.{id} 的頻道授權回呼包含在 Laravel 框架附帶的 BroadcastServiceProvider 內。

翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 上午8:17:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.