简介
在许多现代 Web 应用中,WebSocket 被用于实现实时、实时更新的用户界面。当服务器上的某些数据发生更新时,通常会通过 WebSocket 连接发送一条消息,交由客户端进行处理。相较于持续轮询应用服务器以获取应反映在界面上的数据变更,WebSocket 提供了一种更高效的替代方案。
例如,设想你的应用能够将用户数据导出为 CSV 文件并发送邮件给用户。然而,生成该 CSV 文件需要数分钟时间,因此你选择在一个 排队任务中创建并发送该 CSV。当 CSV 创建完成并发送给用户后,我们可以利用事件广播派发一个 App\Events\UserDataExported 事件,该事件会被应用的 JavaScript 接收。一旦收到该事件,我们就可以向用户显示一条提示,告知其 CSV 已通过邮件发送,而用户无需刷新页面。
为了帮助你构建此类功能,Laravel 使通过 WebSocket 连接“广播”你的服务端 Laravel 事件变得十分简便。广播 Laravel 事件可以让你在服务端 Laravel 应用和客户端 JavaScript 应用之间共享相同的事件名称与数据。
广播背后的核心概念很简单:客户端在前端连接到指定的频道,而你的 Laravel 应用在后端将事件广播到这些频道。这些事件可以包含任何你希望提供给前端的附加数据。
支持的驱动
默认情况下,Laravel 内置了四个可供你选择的服务端广播驱动:Laravel Reverb、Pusher Channels、Ably 以及 Mercure。
在深入了解事件广播之前,请确保你已经阅读了 Laravel 关于 事件与监听器的文档。
快速入门
默认情况下,新创建的 Laravel 应用并未启用广播。你可以使用 install:broadcasting Artisan 命令来启用广播:
php artisan install:broadcastinginstall:broadcasting 命令会提示你选择希望使用的事件广播服务。此外,它还会创建 config/broadcasting.php 配置文件以及 routes/channels.php 文件,你可以在其中注册应用的广播授权路由与回调。
Laravel 开箱即用地支持多种广播驱动:Laravel Reverb、Pusher Channels、Ably、Mercure,以及用于本地开发与调试的 log 驱动。此外,还包含一个 null 驱动,可用于在测试期间禁用广播。config/broadcasting.php 配置文件中为每个驱动都提供了配置示例。
你应用的所有事件广播配置都存储在 config/broadcasting.php 配置文件中。若你的应用中不存在此文件,也无需担心;当你运行 install:broadcasting Artisan 命令时,该文件将被创建。
后续步骤
一旦启用了事件广播,你就可以进一步学习如何定义广播事件以及监听事件。如果你使用的是 Laravel 的 React、Vue 或 Svelte 入门套件,可以使用 Echo 的 useEcho 钩子来监听事件。
在广播任何事件之前,你应当首先配置并运行一个 队列工作进程。所有事件广播均通过排队任务执行,以避免应用的响应时间受到事件广播的严重影响。Mercure 驱动是一个例外,它默认以同步方式广播事件。
服务端安装
要开始使用 Laravel 的事件广播功能,我们需要在 Laravel 应用中进行一些配置,并安装一些包。
事件广播是通过服务端广播驱动来实现的,该驱动会广播你的 Laravel 事件,以便 Laravel Echo(一个 JavaScript 库)能够在浏览器客户端中接收这些事件。不必担心——我们会逐步讲解安装过程的每个部分。
Reverb
若希望在使用 Reverb 作为事件广播器的同时快速启用对 Laravel 广播功能的支持,请带上 --reverb 选项调用 install:broadcasting Artisan 命令。该 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 文档中找到详细的 Reverb 安装与使用说明。
Pusher Channels
若希望在使用 Pusher 作为事件广播器的同时快速启用对 Laravel 广播功能的支持,请带上 --pusher 选项调用 install:broadcasting Artisan 命令。该 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 和应用 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 了,它会在客户端接收广播事件。
加密私有频道
如果你打算使用端到端加密的私有频道,应在 pusher 连接的 options 数组中添加一个 encryption_master_key_base64 选项,其值为经过 base64 编码的 32 字节密钥:
'options' => [
// ...
'encryption_master_key_base64' => env('PUSHER_ENCRYPTION_MASTER_KEY'),
],你可以使用 openssl 命令生成合适的密钥:
openssl rand -base64 32Ably
以下文档将讨论如何在“Pusher 兼容模式”下使用 Ably。不过,Ably 团队推荐并维护了一套广播器与 Echo 客户端,它们能够充分利用 Ably 所提供的独特能力。如需了解使用 Ably 维护的驱动的更多信息,请 查阅 Ably 的 Laravel 广播器文档。
若希望在以 Ably 作为事件广播器的同时快速启用对 Laravel 广播功能的支持,请带上 --ably 选项调用 install:broadcasting Artisan 命令。该 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 配置示例,便于你快速指定密钥。通常,此值应通过 ABLY_KEY 环境变量 进行设置:
ABLY_KEY=your-ably-key然后,在应用程序的 .env 文件中将 BROADCAST_CONNECTION 环境变量设置为 ably:
BROADCAST_CONNECTION=ably最后,你就可以安装并配置 Laravel Echo,它会在客户端接收广播事件。
Mercure
要在使用 Mercure 作为事件广播器时快速启用对 Laravel 广播功能的支持,请使用 --mercure 选项执行 install:broadcasting Artisan 命令。该 Artisan 命令会提示你输入 Mercure 凭证,安装 Mercure PHP 和 JavaScript SDK,并使用相应的变量更新应用程序的 .env 文件:
php artisan install:broadcasting --mercure手动安装
若要手动安装 Mercure 支持,你需要安装 Symfony Mercure 组件和 JWT 库:
composer require symfony/mercure:^0.8 web-token/jwt-library:^4.1接下来,你需要在应用程序的 .env 文件中配置 Mercure 连接:
BROADCAST_CONNECTION=mercure
MERCURE_URL=https://mercure.example.com/.well-known/mercure
MERCURE_PUBLIC_URL=https://mercure.example.com/.well-known/mercure
MERCURE_JWT_SECRET=<your-mercure-jwt-secret>MERCURE_URL 的值是 Laravel 用于发布更新的 URL,而 MERCURE_PUBLIC_URL 是浏览器客户端用于订阅的 URL。你的 Mercure hub 必须使用相同的 JWT 密钥进行配置。
要使用端到端加密的私有频道,请配置一个 32 字节的 MERCURE_ENCRYPTION_KEY 环境变量:
MERCURE_ENCRYPTION_KEY=<your-32-byte-encryption-key>最后,你就可以安装并配置 Laravel Echo,它会在客户端接收广播事件。
FrankenPHP 的内置 Hub
FrankenPHP 包含一个内置的 Mercure hub。当你的应用程序由 FrankenPHP 提供服务时,你可以将 MERCURE_URL 和 MERCURE_PUBLIC_URL 环境变量留空。此时 Laravel 会直接将更新发布到 hub,无需发起任何 HTTP 请求,浏览器则会在应用程序的域名下订阅 /.well-known/mercure。在使用 Laravel Octane 时,内置 hub 会自动配置。
只有由 FrankenPHP 提供服务的请求才能直接发布到内置 hub。若要从其他进程(如 Artisan 命令或队列工作进程)广播事件,请配置 hub 的 URL:
MERCURE_URL="${APP_URL}/.well-known/mercure"同步广播
与其他驱动不同,Mercure 驱动会立即广播事件,而不是将它们放入队列,因此不需要队列工作进程。由于 hub 会异步将更新分发给订阅者,发布更新的速度很快。若要改用队列来广播事件,请将 MERCURE_ASYNC 环境变量设置为 true:
MERCURE_ASYNC=true由于队列工作进程通过 HTTP 发布更新,MERCURE_ASYNC 需要设置 MERCURE_URL 环境变量。在使用 FrankenPHP 的内置 hub 时,请按照上述说明将其设置为 hub 的 URL。
客户端安装
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",
// 密钥:import.meta.env.VITE_PUSHER_APP_KEY,
// 集群:import.meta.env.VITE_PUSHER_APP_CLUSTER,
// 强制使用TLS:true,
// WebSocket主机:import.meta.env.VITE_PUSHER_HOST,
// WebSocket端口:import.meta.env.VITE_PUSHER_PORT,
// WSS端口:import.meta.env.VITE_PUSHER_PORT,
// 启用的传输方式:["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 广播器文档。
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 应用程序设置仪表板的“协议适配器设置”部分启用此功能。
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",
// 密钥:import.meta.env.VITE_ABLY_PUBLIC_KEY,
// WebSocket主机:"realtime-pusher.ably.io",
// WebSocket端口:443,
// 禁用统计信息:true,
// 加密传输:true,
});import { configureEcho } from "@laravel/echo-vue";
configureEcho({
broadcaster: "ably",
// 密钥:import.meta.env.VITE_ABLY_PUBLIC_KEY,
// WebSocket主机:"realtime-pusher.ably.io",
// WebSocket端口:443,
// 禁用统计信息:true,
// 加密传输:true,
});import { configureEcho } from "@laravel/echo-svelte";
configureEcho({
broadcaster: "ably",
// 密钥:import.meta.env.VITE_ABLY_PUBLIC_KEY,
// WebSocket主机:"realtime-pusher.ably.io",
// WebSocket端口:443,
// 禁用统计信息:true,
// 加密传输:true,
});你可能已经注意到,我们的 Ably Echo 配置引用了 VITE_ABLY_PUBLIC_KEY 环境变量。该变量的值应该是你的 Ably 公钥。你的公钥是 Ably 密钥中位于 : 字符之前的部分。
根据你的需求调整好 Echo 配置后,就可以编译应用程序的资源了:
npm run dev 若要了解有关编译应用程序 JavaScript 资源的更多信息,请查阅关于 Vite 的文档。
Mercure
要在 Laravel Echo 中使用 Mercure,请安装 laravel-echo 包:
npm install --save-dev laravel-echo接下来,使用 mercure 广播器创建一个 Echo 实例。host 选项默认为当前源下的 /.well-known/mercure:
import Echo from 'laravel-echo';
window.Echo = new Echo({
broadcaster: 'mercure',
host: import.meta.env.VITE_MERCURE_HUB_URL,
});import { configureEcho } from "@laravel/echo-react";
configureEcho({
broadcaster: "mercure",
});import { configureEcho } from "@laravel/echo-vue";
configureEcho({
broadcaster: "mercure",
});import { configureEcho } from "@laravel/echo-svelte";
configureEcho({
broadcaster: "mercure",
});在 .env 文件中定义中心 URL:
VITE_MERCURE_HUB_URL="${MERCURE_PUBLIC_URL}"概念概述
Laravel 的事件广播允许你使用基于驱动程序的方法,将服务器端的 Laravel 事件广播到客户端的 JavaScript 应用程序中。目前,Laravel 自带 Laravel Reverb、Pusher Channels、Ably 和 Mercure 等驱动程序。可以使用 Laravel Echo JavaScript 包在客户端轻松消费这些事件。
事件通过“频道”进行广播,频道可指定为公开或私有。应用程序的任何访问者都可以订阅公开频道,无需任何身份验证或授权;然而,若要订阅私有频道,用户必须经过身份验证并获得授权才能在该频道上监听。
使用示例应用程序
在深入探讨事件广播的各个组件之前,让我们以一家电商店铺为例,进行一个高层次的概述。
在我们的应用程序中,假设有一个页面允许用户查看其订单的配送状态。再假设当应用程序处理配送状态更新时,会触发一个 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 方法。该方法负责返回事件应该广播的频道。生成的事件类中已经定义了该方法的空存根,所以我们只需要填充其具体细节。我们只希望订单的创建者能够查看状态更新,因此我们将在与订单绑定的私有频道上广播该事件:
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 钩子让上手变得十分简单,默认情况下,事件的所有公开属性都会被包含在广播事件中:
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 接口。该接口已被框架生成的所有事件类默认引入,因此你可以很方便地将其添加到任意事件中。
ShouldBroadcast 接口要求你实现一个方法:broadcastOn。broadcastOn 方法应返回该事件需要广播的频道或频道数组。这些频道应当是 Channel、PrivateChannel 或 PresenceChannel 的实例。Channel 实例代表任何用户都可以订阅的公开频道,而 PrivateChannels 和 PresenceChannels 则代表需要频道授权的私有频道:
<?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 会使用事件的类名进行广播。不过,你也可以在事件中定义 broadcastAs 方法,从而自定义广播名称:
/**
* 事件的广播名称。
*/
public function broadcastAs(): string
{
return 'server.created';
}如果你通过 broadcastAs 方法自定义了广播名称,就需要确保在注册监听器时使用一个前置的 . 字符。这样会让 Echo 不会在事件前添加应用的命名空间:
.listen('.server.created', function (e) {
// ……
});广播数据
当事件被广播时,其所有 public 属性会自动序列化,并作为事件的负载进行广播,从而让你的 JavaScript 应用能够访问其中任意公开数据。因此,举例来说,如果你的事件拥有一个单独的公开 $user 属性,其中包含一个 Eloquent 模型,那么事件的广播负载将会是:
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}不过,如果你希望对广播负载进行更细粒度的控制,也可以在事件中添加一个 broadcastWith 方法。该方法应返回你想要作为事件负载进行广播的数据数组:
/**
* 获取需要广播的数据。
*
* @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';
}如果你希望所有广播事件都使用同一个队列,且不想逐个自定义每个事件类,也可以将 ShouldBroadcast 契约路由到某个队列。
如果你希望通过 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 请求会自动发起。
当安装了广播(broadcasting)后,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 路由类似,频道路由也可以利用隐式和显式的路由模型绑定。例如,你可以不再接收字符串或数字形式的订单 ID,而是直接请求真实的 Order 模型实例:
use App\Models\Order;
use App\Models\User;
Broadcast::channel('orders.{order}', function (User $user, Order $order) {
return $user->id === $order->user_id;
}); 与 HTTP 路由模型绑定不同,频道模型绑定不支持自动的隐式模型绑定作用域。不过这很少成为问题,因为大多数频道都可以基于单个模型的唯一主键进行作用域限制。
授权回调身份验证
私密频道和在线状态(presence)广播频道会通过应用的默认身份验证守卫对当前用户进行身份验证。如果用户未通过身份验证,频道授权会被自动拒绝,授权回调也永远不会执行。不过,你可以在必要时指定多个自定义守卫来对传入请求进行身份验证:
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\InteractsWithSockets trait,才能调用 toOthers 方法。
配置
当你初始化一个 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:
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 门面允许你广播“匿名事件”:
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"
}如果你希望在私有或在线(presence)频道上广播匿名事件,可以使用 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}`);如果你希望离开某个频道,同时也离开与之关联的私有频道和在线(presence)频道,可以调用 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 钩子,能让你轻松地监听事件。要开始使用,请调用用于监听私有事件的 useEcho 钩子。当使用该钩子的组件卸载时,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 钩子会自动离开频道;不过,你可以使用其返回的函数,在必要时以编程方式手动停止或开始监听频道:
import { useEcho } from "@laravel/echo-react";
const { leaveChannel, leave, stopListening, listen } = useEcho(
`orders.${orderId}`,
"OrderShipmentStatusUpdated",
(e) => {
console.log(e.order);
},
);
// 停止监听但未离开频道……
stopListening();
// 重新开始监听……
listen();
// 离开频道……
leaveChannel();
// 离开某个频道,同时也离开与之关联的私有频道和在线(presence)频道……
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();
// 离开某个频道,同时也离开与之关联的私有频道和在线(presence)频道……
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();
// 离开某个频道,同时也离开与之关联的私有频道和在线(presence)频道……
leave();
</script>连接公共频道
要连接公共频道,可以使用 useEchoPublic 钩子:
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 钩子:
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 钩子获取当前的 WebSocket 连接状态,该钩子提供响应式状态,在连接状态发生变化时会自动更新:
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- 连接失败且不会重试。
Socket ID
可以使用 useSocketId 钩子获取当前的 WebSocket socket ID,该钩子提供响应式值,当连接以新的 socket ID 重新连接时会自动更新:
import { useSocketId } from "@laravel/echo-react";
function SocketIndicator() {
const socketId = useSocketId();
return <div>Socket ID: {socketId}</div>;
}<script setup lang="ts">
import { useSocketId } from "@laravel/echo-vue";
const socketId = useSocketId();
</script>
<template>
<div>Socket ID: {{ socketId }}</div>
</template><script>
import { useSocketId } from "@laravel/echo-svelte";
const socketId = useSocketId();
</script>
<div>Socket ID: {socketId()}</div>在线频道
在线频道建立在私密频道的安全性之上,同时额外提供了知晓当前订阅该频道用户的能力。这使得构建强大、协作的应用功能变得简单,例如当其他用户正在查看同一页面时通知用户,或者列出聊天室中的成员。
授权在线频道
所有在线频道同时也是私密频道,因此用户必须获得授权才能访问它们。不过,在定义在线频道的授权回调时,如果用户获准加入频道,不要返回 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 实例:
/**
* Get the channels the event should broadcast on.
*
* @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) => {
// ...
});加密的私密频道
私密频道确保只有获授权的用户才能监听某个频道。然而,事件数据本身仍然会以明文形式通过广播服务传输。当使用 Pusher Channels 或 Mercure 时,可以使用端到端加密的私密频道,这样只有你的应用及其授权客户端能够读取事件数据。
要开始使用,请为 Pusher Channels 或 Mercure 配置一个加密密钥。然后,在事件的 broadcastOn 方法中返回一个 EncryptedPrivateChannel 实例:
use Illuminate\Broadcasting\EncryptedPrivateChannel;
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new EncryptedPrivateChannel('orders.'.$this->order->id),
];
}加密的私密频道的授权方式与私密频道完全一致,因此,在应用的 routes/channels.php 文件中定义的 orders.{orderId} 授权回调,同样也会授权加密的 orders.1 频道。
在你的 JavaScript 应用中,可以使用 Echo 的 encryptedPrivate 方法订阅该频道:
Echo.encryptedPrivate(`orders.${orderId}`)
.listen('OrderShipmentStatusUpdated', (e) => {
console.log(e.order);
});当使用 Pusher Channels 时,默认的 pusher-js 构建版本不包含解密消息所需的代码。因此,在配置 Echo 时,你应该导入 with-encryption 构建版本:
import Pusher from 'pusher-js/with-encryption';
window.Pusher = Pusher;模型广播
在阅读以下有关模型广播的文档之前,我们建议你先熟悉 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 目录中的“实际”事件并不关联,它们会依据约定被赋予名称和载荷。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],
};
}监听模型广播
一旦你在模型中添加了 BroadcastsEvents trait 并定义了模型的 broadcastOn 方法,就可以开始在客户端应用中监听广播的模型事件了。在开始之前,你可以参考关于事件监听的完整文档。
首先,使用 private 方法获取一个频道实例,然后调用 listen 方法来监听指定的事件。通常,传递给 private 方法的频道名称应当遵循 Laravel 的模型广播约定。
获取到频道实例后,你可以使用 listen 方法来监听特定事件。由于模型广播事件与应用程序 App\Events 目录中的“实际”事件并不关联,事件名称必须加上 . 作为前缀,以表明它不属于特定命名空间。每个模型广播事件都有一个 model 属性,其中包含了模型所有可广播的属性:
Echo.private(`App.Models.User.${this.user.id}`)
.listen('.UserUpdated', (e) => {
console.log(e.model);
});使用 React、Vue 或 Svelte
如果你使用的是 React、Vue 或 Svelte,可以使用 Laravel Echo 自带的 useEchoModel 钩子,轻松监听模型广播:
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>你还可以指定模型事件载荷数据的结构,从而提供更强的类型安全性和编辑便捷性:
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);
});客户端事件
当使用 Pusher Channels 时,你必须先在 应用仪表盘的“应用设置”部分启用“客户端事件”选项,才能发送客户端事件。
有时,你可能希望向其他已连接的客户端广播事件,而完全不经过 Laravel 应用程序。这对于诸如“正在输入”通知之类的场景特别有用,即你需要提醒应用程序中的用户:另一位用户正在特定界面上输入消息。
若要广播客户端事件,可以使用 Echo 的 whisper 方法:
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>若要监听客户端事件,可以使用 listenForWhisper 方法:
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>通知
通过将事件广播与通知相结合,你的 JavaScript 应用程序可以在新通知产生时即时接收,而无需刷新页面。在开始之前,请务必阅读有关使用广播通知通道的文档。
一旦配置某通知使用广播通道,就可以使用 Echo 的 notification 方法监听广播事件。请注意,通道名称应与接收通知的实体类名一致:
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>在本示例中,所有通过 broadcast 通道发送给 App\Models\User 实例的通知都将被回调接收。App.Models.User.{id} 通道的授权回调已包含在你应用程序的 routes/channels.php 文件中。
停止监听通知
如果你想在不离开通道的情况下停止监听通知,可以使用 stopListeningForNotification 方法:
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);