广播
引言
在许多现代 web 应用中,WebSockets 被用来实现实时的、动态更新的用户界面。当服务器上的某些数据更新时,通常会通过 WebSocket 连接发送消息,供客户端处理。与不断轮询应用服务器以获取应反映在 UI 中的数据变化相比,WebSockets 提供了一种更高效的替代方案。
例如,假设你的应用能够将用户数据导出为 CSV 文件并通过电子邮件发送给用户。然而,创建 CSV 文件需要几分钟时间,因此你选择在【队列任务】中创建并发送 CSV 文件。当 CSV 文件创建并成功发送给用户时,我们可以使用事件广播来分发一个 App\Events\UserDataExported
事件,该事件会被应用的 JavaScript 接收。一旦事件接收到,我们可以显示一条消息,告知用户他们的 CSV 文件已通过电子邮件发送,而无需刷新页面。
为了帮助你构建这些功能,Laravel 使得 “广播” 服务器端的 Laravel 【事件】通过 WebSocket 连接变得简单。广播 Laravel 事件允许你在服务器端的 Laravel 应用和客户端的 JavaScript 应用之间共享相同的事件名称和数据。
广播的核心概念很简单:客户端在前端连接到命名的频道,而 Laravel 应用在后端将事件广播到这些频道。这些事件可以包含你希望在前端展示的任何额外数据。
支持的驱动
默认情况下,Laravel 提供了三种服务器端广播驱动供你选择: Laravel Reverb、 Pusher Channels 和 Ably。
在深入了解事件广播之前,确保你已经阅读了 Laravel 关于 事件和监听器 的文档。 |
服务器端安装
要开始使用 Laravel 的事件广播,我们需要在 Laravel 应用程序中进行一些配置,并安装一些包。
事件广播是通过服务器端的广播驱动完成的,该驱动将你的 Laravel 事件广播出去,以便 Laravel Echo(一个 JavaScript 库)可以在浏览器客户端接收这些事件。别担心,我们将逐步介绍安装过程的每个部分。
配置
所有应用程序的事件广播配置都存储在 config/broadcasting.php
配置文件中。如果这个目录在你的应用程序中不存在,不用担心;当你运行 install:broadcasting
Artisan 命令时,它会被创建。
Laravel 默认支持几种广播驱动: Laravel Reverb、 Pusher Channels、 Ably,以及一个用于本地开发和调试的 log
驱动。此外,还包括一个空驱动(null
driver),允许你在测试期间禁用广播。每个驱动的配置示例都包含在 config/broadcasting.php
配置文件中。
Reverb
运行 install:broadcasting
命令时,系统会提示你安装 Laravel Reverb。当然,你也可以通过 Composer 包管理器手动安装 Reverb。
composer require laravel/reverb
安装完包后,你可以运行 Reverb 的安装命令来发布配置,添加 Reverb 所需的环境变量,并在应用程序中启用事件广播:
php artisan reverb:install
你可以在 【Reverb 文档】中找到详细的安装和使用说明。
Pusher Channels
如果你打算使用 【Pusher Channels】 来广播事件,你应该通过 Composer 包管理器安装 Pusher Channels PHP SDK:
composer require pusher/pusher-php-server
接下来,你需要在 config/broadcasting.php
配置文件中配置你的 Pusher Channels 凭证。该文件已经包含了 Pusher Channels 的示例配置,允许你快速指定你的密钥、秘密和应用程序 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 支持的其它选项,比如集群。
然后,在应用程序的 .env
文件中将 BROADCAST_CONNECTION
环境变量设置为 pusher
:
BROADCAST_CONNECTION=pusher
最后,你可以安装并配置 Laravel Echo,它将在客户端接收广播事件。
Ably
下面的文档讨论了如何在 “Pusher 兼容” 模式下使用 Ably。然而,Ably 团队推荐并维护一个广播器和 Echo 客户端,这些客户端能够利用 Ably 提供的独特功能。有关使用 Ably 维护的驱动程序的更多信息,请参阅 【Ably 的 Laravel 广播器文档】。 |
如果你打算使用 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,它将在客户端接收广播事件。
客户端安装
Reverb
Laravel Echo 是一个 JavaScript 库,使你能够轻松地订阅频道并监听由服务器端广播驱动程序广播的事件。你可以通过 NPM 包管理器安装 Echo。在这个示例中,我们还将安装 pusher-js
包,因为 Reverb 使用 Pusher 协议来进行 WebSocket 订阅、频道和消息:
npm install --save-dev laravel-echo pusher-js
安装 Echo 后,你可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。一个很好的地方是在 Laravel 框架自带的 resources/js/bootstrap.js
文件的底部。默认情况下,这个文件中已经包含了一个示例 Echo 配置——你只需要取消注释并将广播器配置选项更新为 reverb
:
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,
wssPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
接下来,你应该编译你的应用程序资源:
npm run build
Laravel Echo 的 |
Pusher Channels
Laravel Echo 是一个 JavaScript 库,使你能够轻松地订阅频道并监听由服务器端广播驱动程序广播的事件。Echo 还利用了 pusher-js
NPM 包,使用 Pusher 协议实现 WebSocket 订阅、频道和消息。
install:broadcasting
Artisan 命令会自动为你安装 laravel-echo
和 pusher-js
包;不过,你也可以通过 NPM 手动安装这些包:
npm install --save-dev laravel-echo pusher-js
安装 Echo 后,你可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。install:broadcasting
命令会在 resources/js/echo.js
中创建一个 Echo 配置文件;但是,文件中的默认配置是针对 Laravel Reverb 的。你可以复制下面的配置,将你的配置切换到 Pusher:
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
});
接下来,你应该在应用程序的 .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: 'your-pusher-channels-key'
}
window.Echo = new Echo({
...options,
client: new Pusher(options.key, options)
});
Ably
以下文档讨论了如何在 “Pusher 兼容模式” 下使用 Ably。然而,Ably 团队推荐并维护了一个广播器和 Echo 客户端,可以利用 Ably 提供的独特功能。有关使用 Ably 维护的驱动程序的更多信息,请参考 【Ably 的 Laravel 广播器文档】。 |
Laravel Echo 是一个 JavaScript 库,使你能够轻松地订阅频道并监听由服务器端广播驱动程序广播的事件。Echo 还利用了 pusher-js
NPM 包,使用 Pusher 协议实现 WebSocket 订阅、频道和消息。
install:broadcasting
Artisan 命令会自动为你安装 laravel-echo
和 pusher-js
包;不过,你也可以通过 NPM 手动安装这些包:
npm install --save-dev laravel-echo pusher-js
在继续之前,你应该在 Ably 应用程序的设置中启用 Pusher 协议支持。你可以在 Ably 应用程序的设置仪表板的“协议适配器设置”部分启用此功能。
安装 Echo 后,你可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。install:broadcasting
命令会在 resources/js/echo.js
中创建一个 Echo 配置文件;但是,文件中的默认配置是针对 Laravel Reverb 的。你可以复制下面的配置,将你的配置切换到 Ably:
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,
});
你可能已经注意到我们的 Ably Echo 配置引用了 VITE_ABLY_PUBLIC_KEY
环境变量。此变量的值应该是你的 Ably 公钥。公钥是 Ably 密钥中冒号(:)前的部分。
调整完 Echo 配置后,你可以编译应用程序的资源:
npm run dev
有关如何编译应用程序 JavaScript 资源的更多信息,请参考 Vite 的文档。 |
概念概述
Laravel 的事件广播功能允许你通过基于驱动的 WebSockets 方式,将服务器端的 Laravel 事件广播到客户端的 JavaScript 应用程序。目前,Laravel 提供了 Pusher Channels 和 Ably 驱动。事件可以通过 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
{
/**
* The order instance.
*
* @var \App\Models\Order
*/
public $order;
}
ShouldBroadcast
接口要求我们的事件必须定义一个 broadcastOn
方法。这个方法的作用是返回事件应该广播到的频道。生成的事件类已经为我们定义了一个空的 broadcastOn
方法,因此我们只需要填写其具体内容。我们只希望订单的创建者能够查看状态更新,因此我们将事件广播到与订单关联的私人频道:
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
/**
* Get the channel the event should broadcast on.
*/
public function broadcastOn(): Channel
{
return new PrivateChannel('orders.'.$this->order->id);
}
如果你希望事件广播到多个频道,可以返回一个数组:
use Illuminate\Broadcasting\PrivateChannel;
/**
* Get the channels the event should broadcast on.
*
* @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 来实现这一点。首先,我们使用 private
方法来订阅私人频道,然后使用 listen
方法来监听 OrderShipmentStatusUpdated
事件。默认情况下,所有事件的公共属性都会包含在广播事件中:
Echo.private(`orders.${orderId}`)
.listen('OrderShipmentStatusUpdated', (e) => {
console.log(e.order);
});
通过这种方式,客户端可以实时接收到订单的运输状态更新,而不需要手动刷新页面。
定义广播事件
要告诉 Laravel 某个事件应该被广播,您必须在事件类中实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast
接口。这个接口已经在框架生成的所有事件类中被自动导入,因此您可以轻松地将其添加到任何事件中。
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;
/**
* Create a new event instance.
*/
public function __construct(
public User $user,
) {}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('user.'.$this->user->id),
];
}
}
在实现了 ShouldBroadcast
接口之后,您只需要像往常一样【触发事件】。一旦事件被触发,【队列任务】将自动使用您指定的广播驱动程序来广播该事件。
广播名称
默认情况下,Laravel 会使用事件的类名来广播事件。但是,您可以通过在事件中定义 broadcastAs
方法来自定义广播名称:
/**
* The event's broadcast name.
*/
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
方法。该方法应返回您希望广播的数组数据作为事件的有效载荷:
/**
* Get the data to broadcast.
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return ['id' => $this->user->id];
}
广播队列
默认情况下,每个广播事件都会被放置在 queue.php
配置文件中指定的默认队列连接的默认队列上。您可以通过在事件类中定义 connection
和 queue
属性来自定义广播所使用的队列连接和队列名称:
/**
* The name of the queue connection to use when broadcasting the event.
*
* @var string
*/
public $connection = 'redis';
/**
* The name of the queue on which to place the broadcasting job.
*
* @var string
*/
public $queue = 'default';
或者,您也可以通过在事件中定义 broadcastQueue
方法来自定义队列名称:
/**
* The name of the queue on which to place the broadcasting job.
*/
public function broadcastQueue(): string
{
return 'default';
}
如果您希望使用 sync
队列驱动来广播事件而不是默认队列驱动,您可以实现 ShouldBroadcastNow
接口,而不是 ShouldBroadcast
:
<?php
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
// ...
}
广播条件
有时您只想在特定条件为真的时候广播事件。您可以通过在事件类中添加 broadcastWhen
方法来定义这些条件:
/**
* Determine if this event should broadcast.
*/
public function broadcastWhen(): bool
{
return $this->order->value > 100;
}
此方法返回 true
时,事件将被广播;返回 false
时,事件不会广播。
广播与数据库事务
当在数据库事务中分发广播事件时,它们可能会在数据库事务提交之前被队列处理。这样做时,您在数据库事务中所做的任何模型或数据库记录的更新可能还没有反映到数据库中。此外,在事务中创建的任何模型或数据库记录可能在数据库中不存在。如果您的事件依赖于这些模型,广播事件处理时可能会发生意外错误。
如果您的队列连接的 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 时,订阅私有频道的授权请求会自动发起。
当广播功能启用时,Laravel 会自动注册 /broadcasting/auth
路由来处理授权请求。该路由默认会被放置在 web
中间件组中。
定义授权回调
接下来,我们需要定义逻辑来确定当前认证的用户是否能够监听给定的频道。这是在 routes/channels.php
文件中完成的,该文件由 install:broadcasting
Artisan 命令创建。在这个文件中,您可以使用 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 路由模型绑定不同,频道模型绑定不支持自动的【隐式模型绑定作用域】。然而,这通常不会成为问题,因为大多数频道都可以基于单个模型的唯一主键进行作用域限制。 |
定义频道类
如果您的应用程序正在消费许多不同的频道,那么 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
方法将包含您通常会放置在频道授权闭包中的逻辑。您还可以利用频道模型绑定:
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
方法来解决这个问题,这样可以指示广播者不要将事件广播到当前用户。
您的事件必须使用 |
配置
当您初始化一个 Laravel Echo 实例时,系统会为该连接分配一个 socket ID。如果您在 JavaScript 应用中使用全局的 Axios 实例来发送 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
特性:
<?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;
/**
* Create a new event instance.
*/
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"
}
如果您想在私有或存在频道上广播匿名事件,可以使用 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 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(/* ... */);
离开频道
要离开一个频道,您可以在 Echo 实例上调用 leaveChannel
方法:
Echo.leaveChannel(`orders.${this.order.id}`);
如果您希望离开一个频道及其相关的私有频道和存在频道,可以调用 leave
方法:
Echo.leave(`orders.${this.order.id}`);
命名空间
您可能注意到,在上述示例中,我们没有为事件类指定完整的 App\Events
命名空间。这是因为 Echo 会自动假设事件位于 App\Events
命名空间。然而,您可以通过传递 namespace
配置选项来配置根命名空间:
window.Echo = new Echo({
broadcaster: 'pusher',
// ...
namespace: 'App.Other.Namespace'
});
或者,您也可以在使用 Echo 订阅事件时,通过在事件类名前添加 .
来始终指定完整的类名:
Echo.channel('orders')
.listen('.Namespace\\Event\\Class', (e) => {
// ...
});
存在频道
Presence 通道建立在私有通道的安全性基础上,同时提供了额外的功能,即让你知道谁订阅了该通道。这使得构建强大的协作应用功能变得更加容易,例如在用户查看同一页面时通知他们,或者列出聊天室中的在线用户。
授权存在频道
所有的 Presence 通道也是私有通道;因此,用户必须【经过授权才能访问它们】。然而,当为 Presence 通道定义授权回调时,您不会返回 true
来表示用户有权加入该通道。相反,您应该返回一个关于用户的数据数组。
通过授权回调返回的数据将在您的 JavaScript 应用程序中的 Presence 通道事件监听器中可用。如果用户没有权限加入 Presence 通道,您应该返回 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];
}
});
加入存在频道
要加入一个 Presence 通道,您可以使用 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
方法则会在有用户离开通道时执行。 -
error
方法将在身份验证端点返回非 200 的 HTTP 状态码时执行,或者如果返回的 JSON 解析有问题时执行。
广播到存在频道
Presence 通道可以像公共或私有通道一样接收事件。以聊天室为例,我们可能希望将 NewMessage
事件广播到房间的 Presence 通道。为此,我们将在事件的 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
方法来监听发送到 Presence 通道的事件:
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 会自动使用模型的类名和主键标识符作为通道名称,为该模型实例化一个私有通道。
例如,App\Models\User
模型,id
为 1
,将会被转换为一个 Illuminate\Broadcasting\PrivateChannel
实例,其名称为 App.Models.User.1
。当然,除了返回 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
模型,broadcastChannel
方法将返回字符串 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
特性添加到您的模型并定义了模型的 broadcastOn
方法,就可以在客户端应用程序中开始监听广播的模型事件了。在开始之前,您可能希望查阅完整的事件监听文档。
首先,使用 private
方法来获取通道实例,然后调用 listen
方法来监听指定的事件。通常,传递给 private
方法的通道名称应符合 Laravel 的模型广播约定。
一旦获取到通道实例,您可以使用 listen
方法来监听特定的事件。由于模型广播事件并未与应用程序的 App\Events
目录中的 “实际” 事件相关联,因此事件名称必须以 .
前缀开头,表示它不属于特定的命名空间。每个模型广播事件都有一个 model
属性,其中包含模型的所有可广播属性:
Echo.private(`App.Models.User.${this.user.id}`)
.listen('.PostUpdated', (e) => {
console.log(e.model);
});
客户端事件
在使用 Pusher Channels 时,您必须在 【应用程序仪表板】的 "App Settings" 部分启用 "Client Events" 选项,以便发送客户端事件。 |
有时您可能希望将事件广播到其它已连接的客户端,而无需访问您的 Laravel 应用程序。这对于像“正在输入”通知这样的场景特别有用,您希望在应用程序中提醒用户,某个用户正在某个屏幕上输入消息。
要广播客户端事件,您可以使用 Echo 的 whisper
方法:
Echo.private(`chat.${roomId}`)
.whisper('typing', {
name: this.user.name
});
要监听客户端事件,您可以使用 listenForWhisper
方法:
Echo.private(`chat.${roomId}`)
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
通知
通过将事件广播与【通知】配对使用,您的 JavaScript 应用程序可以在不需要刷新页面的情况下实时接收新的通知。在开始之前,请确保阅读有关使用【广播通知通道】的文档。
一旦您配置了使用广播通道的通知,您可以使用 Echo 的 notification
方法来监听广播事件。请记住,通道名称应与接收通知的实体的类名匹配:
Echo.private(`App.Models.User.${userId}`)
.notification((notification) => {
console.log(notification.type);
});
在这个示例中,所有通过广播通道发送给 App\Models\User
实例的通知都会被回调接收。App.Models.User.{id}
通道的授权回调会包含在您的应用程序的 routes/channels.php
文件中。