广播系统
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
广播
简介
在许多现代 Web 应用中,WebSocket 被用来实现实时、动态更新的用户界面。当服务器上的某些数据发生更新时,通常会通过 WebSocket 连接发送一条消息,并由客户端进行处理。相比持续轮询应用服务器来检查需要反映到 UI 中的数据变化,WebSocket 提供了一种更加高效的替代方案。
例如,假设你的应用可以将用户数据导出为 CSV 文件,并通过电子邮件发送给用户。但是,生成这个 CSV 文件需要几分钟时间,因此你决定通过一个 队列任务 来生成并发送 CSV。当 CSV 文件生成完成并发送给用户后,我们可以使用事件广播来触发一个 App\Events\UserDataExported 事件,并由应用中的 JavaScript 接收该事件。一旦接收到这个事件,我们就可以向用户显示一条消息,告知他们 CSV 文件已经通过电子邮件发送,而不需要用户手动刷新页面。
为了帮助你构建这类功能,Laravel 可以轻松地通过 WebSocket 连接“广播”服务端的 Laravel 事件。通过广播 Laravel 事件,你可以在服务端 Laravel 应用和客户端 JavaScript 应用之间共享相同的事件名称和数据。
广播背后的核心概念很简单:客户端在前端连接到指定名称的频道,而 Laravel 应用则在后端向这些频道广播事件。这些事件可以包含任何你希望提供给前端使用的附加数据。
支持的驱动
默认情况下,Laravel 提供了三种服务端广播驱动供你选择:Laravel Reverb、Pusher Channels 和 Ably。
[!注意]
在深入了解事件广播之前,请确保你已经阅读过 Laravel 关于 事件和监听器 的文档。
快速开始
默认情况下,新建的 Laravel 应用并不会启用广播功能。你可以使用 install:broadcasting Artisan 命令来启用广播:
php artisan install:broadcasting
install:broadcasting 命令会提示你选择要使用的事件广播服务。此外,它还会创建 config/broadcasting.php 配置文件以及 routes/channels.php 文件,你可以在其中注册应用的广播授权路由和回调。
Laravel 开箱即用地支持多种广播驱动:Laravel Reverb、Pusher Channels、Ably,以及用于本地开发和调试的 log 驱动。
此外,Laravel 还提供了一个 null 驱动,可以让你在测试期间禁用广播。config/broadcasting.php 配置文件中包含了这些驱动各自的配置示例。
应用中所有与事件广播相关的配置都存储在 config/broadcasting.php 配置文件中。如果你的应用中还不存在这个文件,也不用担心;当你运行 install:broadcasting Artisan 命令时,它会自动创建。
下一步
启用事件广播后,你就可以继续了解如何 定义广播事件 和 监听事件。
如果你正在使用 Laravel 的 React、Vue 或 Svelte 入门套件,你可以通过 Echo 提供的 useEcho Hook 来监听事件。
[!注意]
在广播任何事件之前,你应该先配置并运行一个 队列 Worker。所有事件广播都会通过队列任务执行,这样可以避免事件广播对应用的响应时间造成明显影响。
服务端安装
要开始使用 Laravel 的事件广播功能,我们需要先在 Laravel 应用中完成一些配置,并安装几个相关的软件包。
事件广播是通过服务端的广播驱动实现的。该驱动会广播 Laravel 事件,以便 Laravel Echo(一个 JavaScript 库)能够在浏览器客户端中接收这些事件。不用担心,下面我们会一步一步介绍整个安装过程。
Reverb
如果你希望使用 Reverb 作为事件广播服务,可以通过执行带有 --reverb 选项的 install:broadcasting Artisan 命令,快速启用 Laravel 的广播功能。
这个 Artisan 命令会安装 Reverb 所需的 Composer 和 NPM 软件包,并在应用的 .env 文件中添加或更新相应的环境变量:
php artisan install:broadcasting --reverb
手动安装
运行 install:broadcasting 命令时,系统会提示你安装 Laravel Reverb。当然,你也可以使用 Composer 包管理器手动安装 Reverb:
composer require laravel/reverb
软件包安装完成后,你可以运行 Reverb 的安装命令,用于发布配置文件、添加 Reverb 所需的环境变量,并在应用中启用事件广播:
php artisan reverb:install
你可以在 Reverb 文档 中查看详细的安装和使用说明。
Pusher Channels
如果你希望使用 Pusher 作为事件广播服务,可以通过执行带有 --pusher 选项的 install:broadcasting Artisan 命令,快速启用 Laravel 的广播功能。
这个 Artisan 命令会提示你输入 Pusher 凭据,安装 Pusher 的 PHP 和 JavaScript SDK,并在应用的 .env 文件中添加或更新相应的环境变量:
php artisan install:broadcasting --pusher
手动安装
如果要手动安装 Pusher 支持,你需要使用 Composer 包管理器安装 Pusher Channels PHP SDK:
composer require pusher/pusher-php-server
接下来,你需要在 config/broadcasting.php 配置文件中配置 Pusher Channels 的凭据。
该文件中已经包含了一个 Pusher Channels 的配置示例,你可以快速配置你的 key、secret 和 application ID。
通常情况下,你应该在应用的 .env 文件中配置 Pusher Channels 凭据:
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"
config/broadcasting.php 文件中的 pusher 配置还允许你指定 Channels 支持的其他 options,例如 cluster。
然后,在应用的 .env 文件中,将 BROADCAST_CONNECTION 环境变量设置为 pusher:
BROADCAST_CONNECTION=pusher
最后,你就可以安装并配置 Laravel Echo 了,它将负责在客户端接收广播事件。
Ably
[!NOTE]
下面的文档介绍的是如何以“Pusher 兼容模式”使用 Ably。不过,Ably 团队推荐并维护了专门的广播驱动和 Echo 客户端,可以充分利用 Ably 提供的独特功能。有关如何使用 Ably 官方维护驱动的更多信息,请参阅 Ably 的 Laravel broadcaster 文档。
如果你希望使用 Ably 作为事件广播服务,可以通过执行带有 --ably 选项的 install:broadcasting Artisan 命令,快速启用 Laravel 的广播功能。
这个 Artisan 命令会提示你输入 Ably 凭据,安装 Ably 的 PHP 和 JavaScript SDK,并在应用的 .env 文件中添加或更新相应的环境变量:
php artisan install:broadcasting --ably
继续之前,你需要在 Ably 应用设置中启用 Pusher 协议支持。你可以在 Ably 应用设置面板中的 “Protocol Adapter Settings” 部分启用该功能。
手动安装
如果要手动安装 Ably 支持,你需要使用 Composer 包管理器安装 Ably PHP SDK:
composer require ably/ably-php
接下来,你需要在 config/broadcasting.php 配置文件中配置 Ably 凭据。
该文件中已经包含了一个 Ably 配置示例,你可以快速设置你的 key。通常,这个值应该通过 ABLY_KEY 环境变量 进行设置:
ABLY_KEY=your-ably-key
然后,在应用的 .env 文件中,将 BROADCAST_CONNECTION 环境变量设置为 ably:
BROADCAST_CONNECTION=ably
最后,你就可以安装并配置 Laravel Echo 了,它将负责在客户端接收广播事件。
客户端安装
Reverb
Laravel Echo 是一个 JavaScript 库,可以让你非常方便地订阅频道,并监听由服务端广播驱动发送的事件。
当你通过 install:broadcasting Artisan 命令安装 Laravel Reverb 时,Reverb 和 Echo 所需的脚手架代码及配置会自动添加到你的应用中。
不过,如果你希望手动配置 Laravel Echo,也可以按照下面的说明进行操作。
手动安装
如果要为应用前端手动配置 Laravel Echo,首先需要安装 pusher-js 包,因为 Reverb 使用 Pusher 协议来处理 WebSocket 订阅、频道和消息:
npm install --save-dev laravel-echo pusher-js
安装 Echo 后,就可以在应用的 JavaScript 中创建一个新的 Echo 实例。
一个比较合适的位置是在 Laravel 框架默认提供的 resources/js/app.js 文件底部:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
import { configureEcho } from "@laravel/echo-react";
configureEcho({
broadcaster: "reverb",
// key: import.meta.env.VITE_REVERB_APP_KEY,
// wsHost: import.meta.env.VITE_REVERB_HOST,
// wsPort: import.meta.env.VITE_REVERB_PORT,
// wssPort: import.meta.env.VITE_REVERB_PORT,
// forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
});
import { configureEcho } from "@laravel/echo-vue";
configureEcho({
broadcaster: "reverb",
// key: import.meta.env.VITE_REVERB_APP_KEY,
// wsHost: import.meta.env.VITE_REVERB_HOST,
// wsPort: import.meta.env.VITE_REVERB_PORT,
// wssPort: import.meta.env.VITE_REVERB_PORT,
// forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
});
import { configureEcho } from "@laravel/echo-svelte";
configureEcho({
broadcaster: "reverb",
// key: import.meta.env.VITE_REVERB_APP_KEY,
// wsHost: import.meta.env.VITE_REVERB_HOST,
// wsPort: import.meta.env.VITE_REVERB_PORT,
// wssPort: import.meta.env.VITE_REVERB_PORT,
// forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
});
接下来,你应该编译应用的前端资源:
npm run build
[!警告]
Laravel Echo 的reverb广播器要求使用 laravel-echo v1.16.0 或更高版本。
Pusher Channels
Laravel Echo 是一个 JavaScript 库,可以让你轻松订阅频道,并监听由服务端广播驱动发送的事件。
当你通过 install:broadcasting --pusher Artisan 命令安装广播支持时,Pusher 和 Echo 所需的脚手架代码及配置会自动添加到你的应用中。
不过,如果你希望手动配置 Laravel Echo,也可以按照下面的说明进行操作。
手动安装
如果要为应用前端手动配置 Laravel Echo,首先需要安装 laravel-echo 和 pusher-js 软件包。它们使用 Pusher 协议来处理 WebSocket 订阅、频道和消息:
npm install --save-dev laravel-echo pusher-js
安装 Echo 后,就可以在应用的 resources/js/app.js 文件中创建一个新的 Echo 实例:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
forceTLS: true
});
import { configureEcho } from "@laravel/echo-react";
configureEcho({
broadcaster: "pusher",
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
// forceTLS: true,
// wsHost: import.meta.env.VITE_PUSHER_HOST,
// wsPort: import.meta.env.VITE_PUSHER_PORT,
// wssPort: import.meta.env.VITE_PUSHER_PORT,
// enabledTransports: ["ws", "wss"],
});
import { configureEcho } from "@laravel/echo-vue";
configureEcho({
broadcaster: "pusher",
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
// forceTLS: true,
// wsHost: import.meta.env.VITE_PUSHER_HOST,
// wsPort: import.meta.env.VITE_PUSHER_PORT,
// wssPort: import.meta.env.VITE_PUSHER_PORT,
// enabledTransports: ["ws", "wss"],
});
import { configureEcho } from "@laravel/echo-svelte";
configureEcho({
broadcaster: "pusher",
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
// forceTLS: true,
// wsHost: import.meta.env.VITE_PUSHER_HOST,
// wsPort: import.meta.env.VITE_PUSHER_PORT,
// wssPort: import.meta.env.VITE_PUSHER_PORT,
// enabledTransports: ["ws", "wss"],
});
接下来,你需要在应用的 .env 文件中为 Pusher 环境变量设置正确的值。如果这些变量还不存在于 .env 文件中,则需要手动添加:
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
根据你的应用需求调整好 Echo 配置后,就可以编译应用的前端资源:
npm run build
[!注意]
如果你想进一步了解如何编译应用的 JavaScript 资源,请参阅 Vite 文档。
使用现有的客户端实例
如果你已经有一个预先配置好的 Pusher Channels 客户端实例,并希望让 Echo 直接使用它,可以通过 client 配置项将该实例传递给 Echo:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
const options = {
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY
}
window.Echo = new Echo({
...options,
client: new Pusher(options.key, options)
});
Ably
[!注意]
下面的文档介绍的是如何以“Pusher 兼容模式”使用 Ably。不过,Ably 团队推荐并维护了一套专门的广播驱动和 Echo 客户端,可以充分利用 Ably 提供的独特功能。有关如何使用 Ably 官方维护驱动的更多信息,请参阅 Ably 的 Laravel broadcaster 文档。
Laravel Echo 是一个 JavaScript 库,可以让你轻松订阅频道,并监听由服务端广播驱动发送的事件。
当你通过 install:broadcasting --ably Artisan 命令安装广播支持时,Ably 和 Echo 所需的脚手架代码及配置会自动添加到你的应用中。
不过,如果你希望手动配置 Laravel Echo,也可以按照下面的说明进行操作。
手动安装
如果要为应用前端手动配置 Laravel Echo,首先需要安装 laravel-echo 和 pusher-js 软件包。它们使用 Pusher 协议来处理 WebSocket 订阅、频道和消息:
npm install --save-dev laravel-echo pusher-js
继续之前,你需要在 Ably 应用设置中启用 Pusher 协议支持。你可以在 Ably 应用设置面板中的 “Protocol Adapter Settings” 部分启用该功能。
安装 Echo 后,就可以在应用的 resources/js/app.js 文件中创建一个新的 Echo 实例:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
wsHost: 'realtime-pusher.ably.io',
wsPort: 443,
disableStats: true,
encrypted: true,
});
import { configureEcho } from "@laravel/echo-react";
configureEcho({
broadcaster: "ably",
// key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
// wsHost: "realtime-pusher.ably.io",
// wsPort: 443,
// disableStats: true,
// encrypted: true,
});
import { configureEcho } from "@laravel/echo-vue";
configureEcho({
broadcaster: "ably",
// key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
// wsHost: "realtime-pusher.ably.io",
// wsPort: 443,
// disableStats: true,
// encrypted: true,
});
import { configureEcho } from "@laravel/echo-svelte";
configureEcho({
broadcaster: "ably",
// key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
// wsHost: "realtime-pusher.ably.io",
// wsPort: 443,
// disableStats: true,
// encrypted: true,
});
你可能已经注意到,我们的 Ably Echo 配置中引用了一个 VITE_ABLY_PUBLIC_KEY 环境变量。这个变量的值应该设置为你的 Ably 公钥。你的公钥就是 Ably key 中位于 : 字符之前的那一部分。
根据你的需要调整好 Echo 配置后,就可以编译应用的前端资源:
npm run dev
[!注意]
如果你想进一步了解如何编译应用的 JavaScript 资源,请参阅 Vite 文档。
概念概览
Laravel 的事件广播允许你通过基于驱动的 WebSocket 方式,将服务端 Laravel 事件广播到客户端 JavaScript 应用。
目前,Laravel 内置支持 Laravel Reverb、Pusher Channels 和 Ably 驱动。这些事件可以通过客户端的 Laravel Echo JavaScript 包轻松接收和处理。
事件是通过“频道(channels)”进行广播的,这些频道可以设置为公共频道或私有频道。
任何访问你应用的用户都可以订阅公共频道,而不需要进行任何身份认证或授权;但是,如果要订阅私有频道,用户必须先通过身份认证,并且获得监听该频道的授权。
使用示例应用
在深入了解事件广播的各个组成部分之前,我们先以一个电商应用为例,从整体上了解一下事件广播的工作方式。
假设我们的应用中有一个页面,允许用户查看自己订单的配送状态。
同时假设,当应用处理订单配送状态更新时,会触发一个 OrderShipmentStatusUpdated 事件:
use App\Events\OrderShipmentStatusUpdated;
OrderShipmentStatusUpdated::dispatch($order);
ShouldBroadcast 接口
当用户正在查看自己的某个订单时,我们不希望他们为了查看状态更新而手动刷新页面。相反,我们希望这些更新在产生时就自动广播到应用中。
因此,我们需要让 OrderShipmentStatusUpdated 事件实现 ShouldBroadcast 接口。这样,当事件被触发时,Laravel 就会自动对其进行广播:
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class OrderShipmentStatusUpdated implements ShouldBroadcast
{
/**
* 订单实例。
*
* @var \App\Models\Order
*/
public $order;
}
ShouldBroadcast 接口要求事件必须定义一个 broadcastOn 方法。这个方法负责返回该事件应该广播到哪些频道。在通过 Laravel 生成的事件类中,通常已经包含了这个方法的空实现,因此我们只需要补充具体逻辑即可。由于我们只希望订单的创建者能够查看订单状态更新,因此这里会将事件广播到一个与该订单绑定的私有频道:
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
/**
* 获取事件应该广播到的频道。
*/
public function broadcastOn(): Channel
{
return new PrivateChannel('orders.'.$this->order->id);
}
如果你希望事件同时广播到多个频道,也可以返回一个 array:
use Illuminate\Broadcasting\PrivateChannel;
/**
* 获取事件应该广播到的频道。
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('orders.'.$this->order->id),
// ...
];
}
授权频道
请记住,用户必须经过授权后才能监听私有频道。我们可以在应用的 routes/channels.php 文件中定义频道授权规则。在这个示例中,我们需要验证任何尝试监听私有 orders.1 频道的用户,是否确实是该订单的创建者:
use App\Models\Order;
use App\Models\User;
Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
channel 方法接收两个参数:频道名称,以及一个返回 true 或 false 的回调函数,用来表示当前用户是否有权限监听该频道。所有授权回调的第一个参数都会接收到当前已认证的用户,后续参数则会接收频道名称中的其他通配符参数。在这个示例中,我们使用 {orderId} 占位符来表示频道名称中的 “ID” 部分是一个通配符。
监听事件广播
接下来,我们只需要在 JavaScript 应用中监听这个事件即可。
我们可以使用 Laravel Echo 来完成。Laravel Echo 内置的 React、Vue 和 Svelte Hooks 可以让你非常方便地开始监听事件。默认情况下,事件中的所有公共属性都会包含在广播事件中:
import { useEcho } from "@laravel/echo-react";
useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";
useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
</script>
<script>
import { useEcho } from "@laravel/echo-svelte";
useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
</script>
定义广播事件
要告诉 Laravel 某个事件需要被广播,你必须让该事件类实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast 接口。
Laravel 框架生成的所有事件类中都已经导入了这个接口,因此你可以很方便地将它添加到任何事件中。ShouldBroadcast 接口要求你实现一个方法:broadcastOn。broadcastOn 方法应该返回一个频道,或者返回一个频道数组,用来指定该事件应该广播到哪些频道。这些频道应该是 Channel、PrivateChannel 或 PresenceChannel 的实例。Channel 实例表示公共频道,任何用户都可以订阅;而 PrivateChannel 和 PresenceChannel 表示私有频道,需要进行 频道授权:
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class ServerCreated implements ShouldBroadcast
{
use SerializesModels;
/**
* 创建一个新的事件实例。
*/
public function __construct(
public User $user,
) {}
/**
* 获取事件应该广播到的频道。
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('user.'.$this->user->id),
];
}
}
实现 ShouldBroadcast 接口之后,你只需要像平时一样 触发事件 即可。事件触发后,Laravel 会自动创建一个 队列任务,并使用你配置的广播驱动来广播该事件。
广播名称
默认情况下,Laravel 会使用事件的类名作为广播事件名称。不过,你可以通过在事件类中定义 broadcastAs 方法来自定义广播名称:
/**
* 事件的广播名称。
*/
public function broadcastAs(): string
{
return 'server.created';
}
如果你使用 broadcastAs 方法自定义了广播名称,那么在注册监听器时,需要确保事件名称前面带有一个 . 字符。这样可以告诉 Echo 不要在事件名称前自动添加应用的命名空间:
.listen('.server.created', function (e) {
// ...
});
广播数据
当一个事件被广播时,它所有的 public 属性都会被自动序列化,并作为事件的 payload 进行广播。因此,你可以在 JavaScript 应用中访问这些公开数据。例如,如果你的事件中只有一个公开的 $user 属性,并且该属性包含一个 Eloquent 模型,那么事件广播的 payload 大致如下:
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}
不过,如果你希望更精细地控制广播的 payload,可以在事件类中添加一个 broadcastWith 方法。
这个方法应该返回一个数组,其中包含你希望作为事件 payload 广播的数据:
/**
* 获取需要广播的数据。
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return ['id' => $this->user->id];
}
广播队列
默认情况下,每个广播事件都会被放入 queue.php 配置文件中所指定的默认队列连接和默认队列。
你可以在事件类中使用 Connection 和 Queue 属性,自定义广播器使用的队列连接和队列名称:
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;
#[Connection('redis')]
#[Queue('default')]
class ServerCreated implements ShouldBroadcast
{
// ...
}
或者,你也可以通过在事件类中定义 broadcastQueue 方法来自定义队列名称:
/**
* 用于放置广播任务的队列名称。
*/
public function broadcastQueue(): string
{
return 'default';
}
如果你希望使用 sync 队列来广播事件,而不是使用默认的队列驱动,可以实现 ShouldBroadcastNow 接口,而不是 ShouldBroadcast:
<?php
namespace App\Events;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
// ...
}
广播条件
有时,你可能只希望在某个条件成立时才广播事件。你可以通过在事件类中添加 broadcastWhen 方法来定义这个条件:
/**
* 判断该事件是否应该广播。
*/
public function broadcastWhen(): bool
{
return $this->order->value > 100;
}
广播与数据库事务
当广播事件在数据库事务中被触发时,队列可能会在数据库事务提交之前就开始处理该事件。
在这种情况下,你在数据库事务中对模型或数据库记录所做的更新,可能还没有真正写入数据库。
此外,在事务中创建的模型或数据库记录,也可能还没有实际存在于数据库中。如果你的事件依赖这些模型,那么当广播事件的队列任务被处理时,就可能出现一些意外错误。如果你的队列连接中的 after_commit 配置项被设置为 false,你仍然可以通过让事件类实现 ShouldDispatchAfterCommit 接口,来指定某个广播事件必须等到所有正在进行的数据库事务提交之后再触发:
<?php
namespace App\Events;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Queue\SerializesModels;
class ServerCreated implements ShouldBroadcast, ShouldDispatchAfterCommit
{
use SerializesModels;
}
[!注意]
如果你想进一步了解如何处理这类问题,请参阅有关 队列任务与数据库事务 的文档。
授权频道
私有频道要求你授权当前已通过身份验证的用户确实可以监听该频道。实现这一点的方法是向你的 Laravel 应用程序发送一个包含频道名称的 HTTP 请求,并由应用程序判断用户是否可以监听该频道。使用 Laravel Echo 时,用于授权订阅私有频道的 HTTP 请求将自动发送。
安装广播功能后,Laravel 会尝试自动注册 /broadcasting/auth 路由来处理授权请求。如果 Laravel 无法自动注册这些路由,你可以在应用程序的 /bootstrap/app.php 文件中手动注册:
->withRouting(
web: __DIR__.'/../routes/web.php',
channels: __DIR__.'/../routes/channels.php',
health: '/up',
)
定义授权回调
接下来,我们需要定义实际判断当前已通过身份验证的用户是否可以监听指定频道的逻辑。这需要在 install:broadcasting Artisan 命令创建的 routes/channels.php 文件中完成。在此文件中,你可以使用 Broadcast::channel 方法注册频道授权回调:
use App\Models\User;
Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
channel 方法接受两个参数:频道名称和一个回调函数。该回调函数返回 true 或 false,用于指示用户是否有权监听该频道。
所有授权回调都会将当前已通过身份验证的用户作为第一个参数接收,并将其他通配符参数依次作为后续参数接收。在此示例中,我们使用 {orderId} 占位符来表示频道名称中的“ID”部分是一个通配符。
你可以使用 channel:list Artisan 命令查看应用程序的广播授权回调列表:
php artisan channel:list
授权回调模型绑定
与 HTTP 路由一样,频道路由也可以利用隐式和显式的路由模型绑定。例如,你可以请求一个实际的 Order 模型实例,而不是接收字符串或数字形式的订单 ID:
use App\Models\Order;
use App\Models\User;
Broadcast::channel('orders.{order}', function (User $user, Order $order) {
return $user->id === $order->user_id;
});
[!警告]
与 HTTP 路由模型绑定不同,频道模型绑定不支持自动隐式模型绑定作用域。不过,这很少会成为问题,因为大多数频道都可以根据单个模型的唯一主键来限定作用域。
授权回调身份验证
私有广播频道和在线广播频道通过应用程序的默认身份验证守卫对当前用户进行身份验证。如果用户未通过身份验证,频道授权将自动被拒绝,并且不会执行授权回调。不过,如果有需要,你可以指定多个自定义守卫,用于对传入请求进行身份验证:
Broadcast::channel('channel', function () {
// ...
}, ['guards' => ['web', 'admin']]);
定义频道类
如果应用程序使用了许多不同的频道,routes/channels.php 文件可能会变得臃肿。因此,你可以使用频道类来代替闭包授权频道。要生成频道类,请使用 make:channel Artisan 命令。该命令会在 App/Broadcasting 目录中创建一个新的频道类。
php artisan make:channel OrderChannel
接下来,在你的 routes/channels.php 文件中注册频道:
use App\Broadcasting\OrderChannel;
Broadcast::channel('orders.{order}', OrderChannel::class);
最后,你可以将频道的授权逻辑放在频道类的 join 方法中。此 join 方法将包含通常放在频道授权闭包中的相同逻辑。你也可以利用频道模型绑定:
<?php
namespace App\Broadcasting;
use App\Models\Order;
use App\Models\User;
class OrderChannel
{
/**
* 创建一个新的频道实例。
*/
public function __construct() {}
/**
* 验证用户对该频道的访问权限。
*/
public function join(User $user, Order $order): array|bool
{
return $user->id === $order->user_id;
}
}
[!注意]
与 Laravel 中的许多其他类一样,通道类会由服务容器自动解析。因此,你可以在通道类的构造函数中通过类型提示声明该通道所需的任何依赖项。
广播事件
定义事件并为其实现 ShouldBroadcast 接口后,你只需使用该事件的 dispatch 方法触发事件即可。事件分发器会检测到该事件标记了 ShouldBroadcast 接口,并将该事件加入队列以进行广播:
use App\Events\OrderShipmentStatusUpdated;
OrderShipmentStatusUpdated::dispatch($order);
仅广播给其他人
在构建使用事件广播的应用程序时,你有时可能需要将事件广播给指定频道的所有订阅者,但不包括当前用户。你可以使用 broadcast 辅助函数和 toOthers 方法来实现这一点:
use App\Events\OrderShipmentStatusUpdated;
broadcast(new OrderShipmentStatusUpdated($update))->toOthers();
为了更好地理解何时应该使用 toOthers 方法,我们来设想一个任务列表应用:用户可以通过输入任务名称来创建新任务。
为了创建任务,你的应用可能会向 /task URL 发送请求,该请求会广播任务创建事件,并返回新任务的 JSON 表示形式。当你的 JavaScript 应用收到该接口的响应时,可以直接将新任务添加到任务列表中,如下所示:
axios.post('/task', task)
.then((response) => {
this.tasks.push(response.data);
});
但是,请记住,我们也会广播任务创建事件。如果你的 JavaScript 应用同时监听了此事件,并在收到事件时将任务添加到任务列表中,那么列表中就会出现重复的任务:一个来自接口的响应,另一个来自广播事件。
你可以通过使用 toOthers 方法来解决这个问题,该方法会指示广播系统不要将此事件广播给当前用户。
[!警告]
你的事件类必须使用Illuminate\Broadcasting\InteractsWithSocketsTrait,才能调用toOthers方法。
配置
当你初始化 Laravel Echo 实例时,系统会为该连接分配一个 Socket ID。
如果你在 JavaScript 应用中使用全局的 Axios 实例来发送 HTTP 请求,那么每个发出的请求都会自动附带一个 X-Socket-ID 请求头,其中包含该 Socket ID。
随后,当你调用 toOthers 方法时,Laravel 会从请求头中提取 Socket ID,并指示广播系统不要向具有该 Socket ID 的任何连接广播此事件。
如果你没有使用全局 Axios 实例,则需要手动配置 JavaScript 应用程序,使其在所有发出的请求中都携带 X-Socket-ID 请求头。你可以使用 Echo.socketId 方法获取 Socket ID:
var socketId = Echo.socketId();
自定义连接
如果你的应用程序需要与多个广播连接进行交互,并且你希望使用默认连接以外的广播驱动程序来广播事件,可以使用 via 方法指定要向哪个连接推送事件:
use App\Events\OrderShipmentStatusUpdated;
broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');
或者,你也可以在事件的构造函数中调用 broadcastVia 方法,指定该事件使用的广播连接。不过,在执行此操作之前,你应确保事件类使用了 InteractsWithBroadcasting Trait:
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class OrderShipmentStatusUpdated implements ShouldBroadcast
{
use InteractsWithBroadcasting;
/**
* 创建一个新的事件实例。
*/
public function __construct()
{
$this->broadcastVia('pusher');
}
}
匿名事件
有时,你可能希望向应用程序的前端广播一个简单的事件,而不必专门创建一个事件类。为此,Broadcast Facade 允许你广播匿名事件:
Broadcast::on('orders.'.$order->id)->send();
上述示例将广播以下事件:
{
"event": "AnonymousEvent",
"data": "[]",
"channel": "orders.1"
}
你可以使用 as 和 with 方法自定义事件名称和数据:
Broadcast::on('orders.'.$order->id)
->as('OrderPlaced')
->with($order)
->send();
上面的示例将广播一个类似于以下内容的事件:
{
"event": "OrderPlaced",
"data": "{ id: 1, total: 100 }",
"channel": "orders.1"
}
如果你希望在私有频道(Private Channel)或在线状态频道(Presence Channel)上广播匿名事件,可以使用 private 和 presence 方法:
Broadcast::private('orders.'.$order->id)->send();
Broadcast::presence('channels.'.$channel->id)->send();
使用 send 方法广播匿名事件时,该事件会被分发到应用程序的队列中进行处理。
但是,如果你希望立即广播事件,可以使用 sendNow 方法:
Broadcast::on('orders.'.$order->id)->sendNow();
如果你希望将事件广播给频道的所有订阅者,但排除当前已认证的用户,可以调用 toOthers 方法:
Broadcast::on('orders.'.$order->id)
->toOthers()
->send();
广播异常兜底处理
当应用程序的队列服务器不可用,或者 Laravel 在广播事件时遇到错误,就会抛出异常,通常会导致最终用户看到应用程序错误页面。
由于事件广播通常只是应用程序核心功能的辅助功能,因此,你可以在事件类中实现 ShouldRescue 接口,避免这些异常影响用户体验。
实现了 ShouldRescue 接口的事件,在尝试广播时会自动使用 Laravel 的 rescue 辅助函数。
该辅助函数会捕获所有异常,将异常报告给应用程序的异常处理器以便记录日志,同时允许应用程序继续正常执行,而不会中断用户当前的操作流程:
<?php
namespace App\Events;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldRescue;
class ServerCreated implements ShouldBroadcast, ShouldRescue
{
// ...
}
接收广播
监听事件
完成 Laravel Echo 的安装与实例化 后,就可以开始监听 Laravel 应用程序广播的事件了。首先,使用 channel 方法获取一个频道实例,然后调用 listen 方法监听指定的事件:
Echo.channel(`orders.${this.order.id}`)
.listen('OrderShipmentStatusUpdated', (e) => {
console.log(e.order.name);
});
如果你想监听私有频道上的事件,请改用 private 方法。你可以继续链式调用 listen 方法,以便在同一个频道上监听多个事件:
Echo.private(`orders.${this.order.id}`)
.listen(/* ... */)
.listen(/* ... */)
.listen(/* ... */);
停止监听事件
如果你想停止监听某个事件,但不 离开频道,可以使用 stopListening 方法:
Echo.private(`orders.${this.order.id}`)
.stopListening('OrderShipmentStatusUpdated');
离开频道
要离开某个频道,可以在 Echo 实例上调用 leaveChannel 方法:
Echo.leaveChannel(`orders.${this.order.id}`);
如果你想离开某个频道,同时退出与其关联的私有频道和在线状态频道,可以调用 leave 方法:
Echo.leave(`orders.${this.order.id}`);
命名空间
你可能已经注意到,在上面的示例中,我们没有为事件类指定完整的 App\Events 命名空间。这是因为 Echo 会自动假定这些事件位于 App\Events 命名空间中。不过,你可以在实例化 Echo 时,通过传递 namespace 配置选项来设置根命名空间:
window.Echo = new Echo({
broadcaster: 'pusher',
// ...
namespace: 'App.Other.Namespace'
});
另外,你也可以在使用 Echo 订阅事件时,在事件类名前添加 . 前缀。这样,你就可以始终指定事件类的完整限定类名:
Echo.channel('orders')
.listen('.Namespace\\Event\\Class', (e) => {
// ...
});
使用 React、Vue 或 Svelte
Laravel Echo 提供了适用于 React、Vue 和 Svelte 的 Hooks,让监听事件变得更加简单。
首先,调用 useEcho Hook 来监听私有事件。当使用该 Hook 的组件被卸载时,useEcho 会自动退出相应的频道。
import { useEcho } from "@laravel/echo-react";
useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";
useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
</script>
<script>
import { useEcho } from "@laravel/echo-svelte";
useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
</script>
你也可以向 useEcho 传入一个事件数组,从而同时监听多个事件:
useEcho(
`orders.${orderId}`,
["OrderShipmentStatusUpdated", "OrderShipped"],
(e) => {
console.log(e.order);
},
);
你还可以指定广播事件负载数据的结构类型,从而获得更好的类型安全性,并提升代码编辑时的便利性:
type OrderData = {
order: {
id: number;
user: {
id: number;
name: string;
};
created_at: string;
};
};
useEcho<OrderData>(`orders.${orderId}`, "OrderShipmentStatusUpdated", (e) => {
console.log(e.order.id);
console.log(e.order.user.id);
});
当使用 useEcho Hook 的组件被卸载时,该 Hook 会自动退出相应的频道。
不过,在某些情况下,你可能需要通过编程方式手动控制频道监听的启停。此时,可以使用 useEcho 返回的函数,根据需要手动停止或重新开始监听。
import { useEcho } from "@laravel/echo-react";
const { leaveChannel, leave, stopListening, listen } = useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
// 停止监听,但不退出频道……
stopListening();
// 重新开始监听……
listen();
// 退出频道……
leaveChannel();
// 退出频道,同时退出其关联的私有频道和在线状态频道……
leave();
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";
const { leaveChannel, leave, stopListening, listen } = useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
// 停止监听,但不退出频道……
stopListening();
// 重新开始监听……
listen();
// 退出频道……
leaveChannel();
// 退出频道,同时退出其关联的私有频道和在线状态频道……
leave();
</script>
<script>
import { useEcho } from "@laravel/echo-svelte";
const { leaveChannel, leave, stopListening, listen } = useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
// 停止监听,但不退出频道……
stopListening();
// 重新开始监听……
listen();
// 退出频道……
leaveChannel();
// 退出频道,同时退出其关联的私有频道和在线状态频道……
leave();
</script>
连接到公共频道
要连接到公共频道,可以使用 useEchoPublic Hook:
import { useEchoPublic } from "@laravel/echo-react";
useEchoPublic("posts", "PostPublished", (e) => {
console.log(e.post);
});
<script setup lang="ts">
import { useEchoPublic } from "@laravel/echo-vue";
useEchoPublic("posts", "PostPublished", (e) => {
console.log(e.post);
});
</script>
<script>
import { useEchoPublic } from "@laravel/echo-svelte";
useEchoPublic("posts", "PostPublished", (e) => {
console.log(e.post);
});
</script>
连接到在线状态频道
要连接到在线状态频道,可以使用 useEchoPresence Hook:
import { useEchoPresence } from "@laravel/echo-react";
useEchoPresence("posts", "PostPublished", (e) => {
console.log(e.post);
});
<script setup lang="ts">
import { useEchoPresence } from "@laravel/echo-vue";
useEchoPresence("posts", "PostPublished", (e) => {
console.log(e.post);
});
</script>
<script>
import { useEchoPresence } from "@laravel/echo-svelte";
useEchoPresence("posts", "PostPublished", (e) => {
console.log(e.post);
});
</script>
连接状态
你可以使用 useConnectionStatus Hook 获取当前 WebSocket 连接状态。该 Hook 提供响应式状态,当连接状态发生变化时会自动更新:
import { useConnectionStatus } from "@laravel/echo-react";
function ConnectionIndicator() {
const status = useConnectionStatus();
return <div>Connection: {status}</div>;
}
<script setup lang="ts">
import { useConnectionStatus } from "@laravel/echo-vue";
const status = useConnectionStatus();
</script>
<template>
<div>Connection: {{ status }}</div>
</template>
<script>
import { useConnectionStatus } from "@laravel/echo-svelte";
const status = useConnectionStatus();
</script>
<div>Connection: {status()}</div>
可能的状态值包括:
connected- 已成功连接到 WebSocket 服务器。connecting- 正在进行初始连接尝试。reconnecting- 断开连接后正在尝试重新连接。disconnected- 未连接,且未尝试重新连接。failed- 连接失败,且不会重试。
在线状态频道
在线状态频道(Presence Channels)在私有频道的安全性基础上,提供了额外的功能,使用户能够了解哪些人订阅了该频道。这使得构建强大的协作应用程序功能变得更加容易,例如,当其他用户正在查看同一页面时通知用户,或者列出聊天室中的成员。
授权在线状态频道
所有在线状态频道也都是私有频道;因此,用户必须获得访问授权。但是,在为在线状态频道定义授权回调时,如果用户被授权加入频道,你不应该返回 true。相反,你应该返回一个包含用户相关数据的数组。
授权回调返回的数据将提供给 JavaScript 应用程序中的在线状态频道事件监听器。如果用户未被授权加入在线状态频道,你应该返回 false 或 null:
use App\Models\User;
Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
if ($user->canJoinRoom($roomId)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
加入在线状态频道
要加入在线状态频道,可以使用 Echo 的 join 方法。join 方法将返回一个 PresenceChannel 实现,该实现除了提供 listen 方法外,还允许你订阅 here、joining 和 leaving 事件。
Echo.join(`chat.${roomId}`)
.here((users) => {
// ...
})
.joining((user) => {
console.log(user.name);
})
.leaving((user) => {
console.log(user.name);
})
.error((error) => {
console.error(error);
});
一旦成功加入频道,here 回调就会立即执行,并接收一个数组,其中包含当前订阅该频道的所有其他用户的信息。当新用户加入频道时,joining 方法将被执行;当用户离开频道时,leaving 方法将被执行。当身份验证端点返回的 HTTP 状态码不是 200,或者解析返回的 JSON 时出现问题,error 方法将被执行。
向在线状态频道广播
在线状态频道可以像公共频道或私有频道一样接收事件。以聊天室为例,我们可能希望向聊天室的在线状态频道广播 NewMessage 事件。为此,我们需要从事件的 broadcastOn 方法中返回一个 PresenceChannel 实例:
/**
* 获取事件应该广播到的频道。
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PresenceChannel('chat.'.$this->message->room_id),
];
}
与其他事件一样,你可以使用 broadcast 辅助函数和 toOthers 方法,排除当前用户接收该广播:
broadcast(new NewMessage($message));
broadcast(new NewMessage($message))->toOthers();
与其他类型的事件一样,你可以使用 Echo 的 listen 方法监听发送到在线状态频道的事件:
Echo.join(`chat.${roomId}`)
.here(/* ... */)
.joining(/* ... */)
.leaving(/* ... */)
.listen('NewMessage', (e) => {
// ...
});
模型广播
[!警告]
在阅读以下关于模型广播的文档之前,我们建议你先熟悉 Laravel 模型广播服务的一般概念,以及如何手动创建和监听广播事件。
当应用程序的 Eloquent 模型被创建、更新或删除时,广播事件是一种常见的做法。当然,你可以通过手动为 Eloquent 模型状态变化定义自定义事件,并让这些事件实现 ShouldBroadcast 接口,轻松实现这一功能。
但是,如果你没有在应用程序中将这些事件用于其他目的,那么仅仅为了广播事件而创建事件类可能会比较繁琐。为了解决这个问题,Laravel 允许你指定 Eloquent 模型在状态发生变化时自动进行广播。
首先,你的 Eloquent 模型应该使用 Illuminate\Database\Eloquent\BroadcastsEvents trait。此外,模型还应该定义一个 broadcastOn 方法,该方法将返回一个数组,其中包含模型事件应该广播到的频道:
<?php
namespace App\Models;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Database\Eloquent\BroadcastsEvents;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Post extends Model
{
use BroadcastsEvents, HasFactory;
/**
* 获取文章所属的用户。
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* 获取模型事件应该广播到的频道。
*
* @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
*/
public function broadcastOn(string $event): array
{
return [$this, $this->user];
}
}
一旦你的模型使用了此 trait 并定义了广播频道,当模型实例被创建、更新、删除、移入回收站或恢复时,它就会开始自动广播事件。
此外,你可能已经注意到,broadcastOn 方法接收一个字符串类型的 $event 参数。此参数包含模型上发生的事件类型,其值可能是 created、updated、deleted、trashed 或 restored。通过检查此变量的值,你可以确定模型应该针对特定事件广播到哪些频道(如果有):
/**
* 获取模型事件应该广播到的频道。
*
* @return array<string, array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>>
*/
public function broadcastOn(string $event): array
{
return match ($event) {
'deleted' => [],
default => [$this, $this->user],
};
}
自定义模型广播事件的创建
有时,你可能希望自定义 Laravel 创建底层模型广播事件的方式。你可以通过在 Eloquent 模型中定义 newBroadcastableEvent 方法来实现这一点。此方法应该返回一个 Illuminate\Database\Eloquent\BroadcastableModelEventOccurred 实例:
use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;
/**
* 为模型创建一个新的可广播模型事件。
*/
protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
return (new BroadcastableModelEventOccurred(
$this, $event
))->dontBroadcastToCurrentUser();
}
模型广播约定
频道约定
正如你可能已经注意到的,上面的模型示例中的 broadcastOn 方法并没有返回 Channel 实例,而是直接返回了 Eloquent 模型。如果模型的 broadcastOn 方法返回一个 Eloquent 模型实例(或者该实例包含在方法返回的数组中),Laravel 将自动为该模型实例化一个私有频道,并使用模型的类名和主键标识符作为频道名称。
因此,一个 id 为 1 的 App\Models\User 模型将被转换为一个名为 App.Models.User.1 的 Illuminate\Broadcasting\PrivateChannel 实例。当然,除了从模型的 broadcastOn 方法中返回 Eloquent 模型实例外,你还可以返回完整的 Channel 实例,以便完全控制模型的频道名称:
use Illuminate\Broadcasting\PrivateChannel;
/**
* 获取模型事件应该广播到的频道。
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(string $event): array
{
return [
new PrivateChannel('user.'.$this->id)
];
}
如果你打算从模型的 broadcastOn 方法中显式返回一个频道实例,可以将 Eloquent 模型实例传递给频道的构造函数。这样做时,Laravel 将使用上面讨论的模型频道约定,将 Eloquent 模型转换为频道名称字符串:
return [new Channel($this->user)];
如果你需要确定模型的频道名称,可以在任何模型实例上调用 broadcastChannel 方法。例如,对于一个 id 为 1 的 App\Models\User 模型,此方法将返回字符串 App.Models.User.1:
$user->broadcastChannel();
事件约定
由于模型广播事件并不与应用程序 App\Events 目录中的“实际”事件相关联,因此它们的名称和载荷(payload)是根据约定分配的。Laravel 的约定是使用模型的类名(不包含命名空间)以及触发广播的模型事件名称来广播事件。
因此,例如,对 App\Models\Post 模型进行更新时,将向客户端应用程序广播一个名为 PostUpdated 的事件,其载荷如下:
{
"model": {
"id": 1,
"title": "My first post"
...
},
...
"socket": "someSocketId"
}
删除 App\Models\User 模型时,将广播一个名为 UserDeleted 的事件。
如果你愿意,可以通过在模型中添加 broadcastAs 和 broadcastWith 方法来自定义广播名称和载荷。这些方法接收正在发生的模型事件或操作的名称,使你能够针对每个模型操作自定义事件的名称和载荷。如果 broadcastAs 方法返回 null,Laravel 将使用上面讨论的模型广播事件命名约定来广播事件:
/**
* 模型事件的广播名称。
*/
public function broadcastAs(string $event): string|null
{
return match ($event) {
'created' => 'post.created',
default => null,
};
}
/**
* 获取模型要广播的数据。
*
* @return array<string, mixed>
*/
public function broadcastWith(string $event): array
{
return match ($event) {
'created' => ['title' => $this->title],
default => ['model' => $this],
};
}
Listening for Model Broadcasts
Once you have added the BroadcastsEvents trait to your model and defined your model's broadcastOn method, you are ready to start listening for broadcasted model events within your client-side application. Before getting started, you may wish to consult the complete documentation on listening for events.
First, use the private method to retrieve an instance of a channel, then call the listen method to listen for a specified event. Typically, the channel name given to the private method should correspond to Laravel's model broadcasting conventions.
Once you have obtained a channel instance, you may use the listen method to listen for a particular event. Since model broadcast events are not associated with an "actual" event within your application's App\Events directory, the event name must be prefixed with a . to indicate it does not belong to a particular namespace. Each model broadcast event has a model property which contains all of the broadcastable properties of the model:
Echo.private(`App.Models.User.${this.user.id}`)
.listen('.UserUpdated', (e) => {
console.log(e.model);
});
Using React, Vue, or Svelte
If you are using React, Vue, or Svelte, you may use Laravel Echo's included useEchoModel hook to easily listen for model broadcasts:
import { useEchoModel } from "@laravel/echo-react";
useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
console.log(e.model);
});
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";
useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
console.log(e.model);
});
</script>
<script>
import { useEchoModel } from "@laravel/echo-svelte";
useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
console.log(e.model);
});
</script>
You may also specify the shape of the model event payload data, providing greater type safety and editing convenience:
type User = {
id: number;
name: string;
email: string;
};
useEchoModel<User, "App.Models.User">("App.Models.User", userId, ["UserUpdated"], (e) => {
console.log(e.model.id);
console.log(e.model.name);
});
Client Events
[!NOTE]
When using Pusher Channels, you must enable the "Client Events" option in the "App Settings" section of your application dashboard in order to send client events.
Sometimes you may wish to broadcast an event to other connected clients without hitting your Laravel application at all. This can be particularly useful for things like "typing" notifications, where you want to alert users of your application that another user is typing a message on a given screen.
To broadcast client events, you may use Echo's whisper method:
Echo.private(`chat.${roomId}`)
.whisper('typing', {
name: this.user.name
});
import { useEcho } from "@laravel/echo-react";
const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
console.log('Chat event received:', e);
});
channel().whisper('typing', { name: user.name });
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";
const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
console.log('Chat event received:', e);
});
channel().whisper('typing', { name: user.name });
</script>
<script>
import { useEcho } from "@laravel/echo-svelte";
const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
console.log('Chat event received:', e);
});
channel().whisper('typing', { name: user.name });
</script>
To listen for client events, you may use the listenForWhisper method:
Echo.private(`chat.${roomId}`)
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
import { useEcho } from "@laravel/echo-react";
const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
console.log('Chat event received:', e);
});
channel().listenForWhisper('typing', (e) => {
console.log(e.name);
});
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";
const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
console.log('Chat event received:', e);
});
channel().listenForWhisper('typing', (e) => {
console.log(e.name);
});
</script>
<script>
import { useEcho } from "@laravel/echo-svelte";
const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
console.log('Chat event received:', e);
});
channel().listenForWhisper('typing', (e) => {
console.log(e.name);
});
</script>
Notifications
By pairing event broadcasting with notifications, your JavaScript application may receive new notifications as they occur without needing to refresh the page. Before getting started, be sure to read over the documentation on using the broadcast notification channel.
Once you have configured a notification to use the broadcast channel, you may listen for the broadcast events using Echo's notification method. Remember, the channel name should match the class name of the entity receiving the notifications:
Echo.private(`App.Models.User.${userId}`)
.notification((notification) => {
console.log(notification.type);
});
import { useEchoModel } from "@laravel/echo-react";
const { channel } = useEchoModel('App.Models.User', userId);
channel().notification((notification) => {
console.log(notification.type);
});
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";
const { channel } = useEchoModel('App.Models.User', userId);
channel().notification((notification) => {
console.log(notification.type);
});
</script>
<script>
import { useEchoModel } from "@laravel/echo-svelte";
const { channel } = useEchoModel('App.Models.User', userId);
channel().notification((notification) => {
console.log(notification.type);
});
</script>
In this example, all notifications sent to App\Models\User instances via the broadcast channel would be received by the callback. A channel authorization callback for the App.Models.User.{id} channel is included in your application's routes/channels.php file.
Stop Listening for Notifications
If you would like to stop listening to notifications without leaving the channel, you may use the stopListeningForNotification method:
const callback = (notification) => {
console.log(notification.type);
}
// Start listening...
Echo.private(`App.Models.User.${userId}`)
.notification(callback);
// Stop listening (callback must be the same)...
Echo.private(`App.Models.User.${userId}`)
.stopListeningForNotification(callback);
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
Laravel 13 中文文档
关于 LearnKu
推荐文章: