跳到内容

广播

简介

在许多现代 Web 应用程序中,WebSockets 用于实现实时、动态更新的用户界面。当服务器上的某些数据更新时,通常会通过 WebSocket 连接发送消息,以便客户端处理。WebSockets 提供了一种更有效的替代方案,可以替代持续轮询应用程序的服务器以获取应反映在 UI 中的数据更改。

例如,假设您的应用程序能够将用户的数据导出到 CSV 文件并通过电子邮件发送给他们。但是,创建此 CSV 文件需要几分钟,因此您选择在队列任务中创建 CSV 并通过邮件发送。当 CSV 文件已创建并通过邮件发送给用户后,我们可以使用事件广播来分发 App\Events\UserDataExported 事件,该事件由我们应用程序的 JavaScript 接收。一旦收到事件,我们可以向用户显示一条消息,告知他们的 CSV 文件已通过电子邮件发送给他们,而无需他们刷新页面。

为了帮助您构建这些类型的功能,Laravel 可以轻松地通过 WebSocket 连接“广播”您的服务器端 Laravel 事件。广播您的 Laravel 事件允许您在服务器端 Laravel 应用程序和客户端 JavaScript 应用程序之间共享相同的事件名称和数据。

广播背后的核心概念很简单:客户端连接到前端的命名频道,而您的 Laravel 应用程序将事件广播到后端上的这些频道。这些事件可以包含您希望向前端提供的任何其他数据。

支持的驱动

默认情况下,Laravel 包含三个服务器端广播驱动供您选择:Laravel ReverbPusher ChannelsAbly

在深入研究事件广播之前,请确保您已阅读 Laravel 关于事件和监听器的文档。

服务器端安装

要开始使用 Laravel 的事件广播,我们需要在 Laravel 应用程序中进行一些配置,并安装一些软件包。

事件广播是通过服务器端广播驱动程序完成的,该驱动程序广播您的 Laravel 事件,以便 Laravel Echo(一个 JavaScript 库)可以在浏览器客户端中接收它们。不用担心 - 我们将逐步完成安装过程的每个部分。

配置

您的应用程序的所有事件广播配置都存储在 config/broadcasting.php 配置文件中。如果您的应用程序中不存在此目录,请不要担心;当您运行 install:broadcasting Artisan 命令时,将创建它。

Laravel 开箱即用地支持多个广播驱动程序:Laravel ReverbPusher ChannelsAbly,以及用于本地开发和调试的 log 驱动程序。此外,还包含一个 null 驱动程序,允许您在测试期间禁用广播。config/broadcasting.php 配置文件中包含每个这些驱动程序的配置示例。

安装

默认情况下,新的 Laravel 应用程序中未启用广播。您可以使用 install:broadcasting Artisan 命令启用广播

1php artisan install:broadcasting

install:broadcasting 命令将创建 config/broadcasting.php 配置文件。此外,该命令将创建 routes/channels.php 文件,您可以在其中注册应用程序的广播授权路由和回调。

队列配置

在广播任何事件之前,您应该首先配置并运行队列 worker。所有事件广播都通过队列任务完成,这样您的应用程序的响应时间就不会因广播事件而受到严重影响。

Reverb

当运行 install:broadcasting 命令时,系统将提示您安装 Laravel Reverb。当然,您也可以使用 Composer 包管理器手动安装 Reverb。

1composer require laravel/reverb

安装软件包后,您可以运行 Reverb 的安装命令以发布配置,添加 Reverb 的必需环境变量,并在您的应用程序中启用事件广播

1php artisan reverb:install

您可以在Reverb 文档中找到详细的 Reverb 安装和使用说明。

Pusher Channels

如果您计划使用 Pusher Channels 广播您的事件,则应使用 Composer 包管理器安装 Pusher Channels PHP SDK

1composer require pusher/pusher-php-server

接下来,您应该在 config/broadcasting.php 配置文件中配置您的 Pusher Channels 凭据。此文件中已包含 Pusher Channels 配置示例,允许您快速指定您的密钥、密钥和应用程序 ID。通常,您应该在应用程序的 .env 文件中配置 Pusher Channels 凭据

1PUSHER_APP_ID="your-pusher-app-id"
2PUSHER_APP_KEY="your-pusher-key"
3PUSHER_APP_SECRET="your-pusher-secret"
4PUSHER_HOST=
5PUSHER_PORT=443
6PUSHER_SCHEME="https"
7PUSHER_APP_CLUSTER="mt1"

config/broadcasting.php 文件的 pusher 配置还允许您指定 Channels 支持的其他 options,例如集群。

然后,在应用程序的 .env 文件中将 BROADCAST_CONNECTION 环境变量设置为 pusher

1BROADCAST_CONNECTION=pusher

最后,您已准备好安装和配置 Laravel Echo,它将在客户端接收广播事件。

Ably

以下文档讨论了如何在“Pusher 兼容性”模式下使用 Ably。但是,Ably 团队推荐并维护一个广播器和 Echo 客户端,该客户端能够利用 Ably 提供的独特功能。有关使用 Ably 维护的驱动程序的更多信息,请查阅 Ably 的 Laravel 广播器文档

如果您计划使用 Ably 广播您的事件,则应使用 Composer 包管理器安装 Ably PHP SDK

1composer require ably/ably-php

接下来,您应该在 config/broadcasting.php 配置文件中配置您的 Ably 凭据。此文件中已包含 Ably 配置示例,允许您快速指定您的密钥。通常,此值应通过 ABLY_KEY 环境变量设置

1ABLY_KEY=your-ably-key

然后,在应用程序的 .env 文件中将 BROADCAST_CONNECTION 环境变量设置为 ably

1BROADCAST_CONNECTION=ably

最后,您已准备好安装和配置 Laravel Echo,它将在客户端接收广播事件。

客户端安装

Reverb

Laravel Echo 是一个 JavaScript 库,可以轻松订阅频道并监听服务器端广播驱动程序广播的事件。您可以从 NPM 包管理器安装 Echo。在本例中,我们还将安装 pusher-js 包,因为 Reverb 利用 Pusher 协议进行 WebSocket 订阅、频道和消息传递

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

安装 Echo 后,您就可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。一个很好的位置是在 Laravel 框架附带的 resources/js/bootstrap.js 文件的底部。默认情况下,此文件中已包含 Echo 配置示例 - 您只需取消注释它并将 broadcaster 配置选项更新为 reverb

1import Echo from 'laravel-echo';
2 
3import Pusher from 'pusher-js';
4window.Pusher = Pusher;
5 
6window.Echo = new Echo({
7 broadcaster: 'reverb',
8 key: import.meta.env.VITE_REVERB_APP_KEY,
9 wsHost: import.meta.env.VITE_REVERB_HOST,
10 wsPort: import.meta.env.VITE_REVERB_PORT,
11 wssPort: import.meta.env.VITE_REVERB_PORT,
12 forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
13 enabledTransports: ['ws', 'wss'],
14});

接下来,您应该编译应用程序的资源

1npm run build

Laravel Echo reverb 广播器需要 laravel-echo v1.16.0+。

Pusher Channels

Laravel Echo 是一个 JavaScript 库,可以轻松订阅频道并监听服务器端广播驱动程序广播的事件。Echo 还利用 pusher-js NPM 包来实现 Pusher 协议,用于 WebSocket 订阅、频道和消息传递。

install:broadcasting Artisan 命令会自动为您安装 laravel-echopusher-js 包;但是,您也可以通过 NPM 手动安装这些包

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

安装 Echo 后,您就可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。install:broadcasting 命令在 resources/js/echo.js 中创建一个 Echo 配置文件;但是,此文件中的默认配置适用于 Laravel Reverb。您可以复制以下配置以将您的配置转换为 Pusher

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

接下来,您应该在应用程序的 .env 文件中为 Pusher 环境变量定义适当的值。如果这些变量在您的 .env 文件中尚不存在,则应添加它们

1PUSHER_APP_ID="your-pusher-app-id"
2PUSHER_APP_KEY="your-pusher-key"
3PUSHER_APP_SECRET="your-pusher-secret"
4PUSHER_HOST=
5PUSHER_PORT=443
6PUSHER_SCHEME="https"
7PUSHER_APP_CLUSTER="mt1"
8 
9VITE_APP_NAME="${APP_NAME}"
10VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
11VITE_PUSHER_HOST="${PUSHER_HOST}"
12VITE_PUSHER_PORT="${PUSHER_PORT}"
13VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
14VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

根据应用程序的需要调整 Echo 配置后,您可以编译应用程序的资源

1npm run build

要了解有关编译应用程序 JavaScript 资源的更多信息,请查阅有关 Vite 的文档。

使用现有客户端实例

如果您已经有一个预配置的 Pusher Channels 客户端实例,并且希望 Echo 使用它,则可以通过 client 配置选项将其传递给 Echo

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

Ably

以下文档讨论了如何在“Pusher 兼容性”模式下使用 Ably。但是,Ably 团队推荐并维护一个广播器和 Echo 客户端,该客户端能够利用 Ably 提供的独特功能。有关使用 Ably 维护的驱动程序的更多信息,请查阅 Ably 的 Laravel 广播器文档

Laravel Echo 是一个 JavaScript 库,可以轻松订阅频道并监听服务器端广播驱动程序广播的事件。Echo 还利用 pusher-js NPM 包来实现 Pusher 协议,用于 WebSocket 订阅、频道和消息传递。

install:broadcasting Artisan 命令会自动为您安装 laravel-echopusher-js 包;但是,您也可以通过 NPM 手动安装这些包

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

在继续之前,您应该在 Ably 应用程序设置中启用 Pusher 协议支持。您可以在 Ably 应用程序设置仪表板的“协议适配器设置”部分中启用此功能。

安装 Echo 后,您就可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。install:broadcasting 命令在 resources/js/echo.js 中创建一个 Echo 配置文件;但是,此文件中的默认配置适用于 Laravel Reverb。您可以复制以下配置以将您的配置转换为 Ably

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

您可能已经注意到我们的 Ably Echo 配置引用了 VITE_ABLY_PUBLIC_KEY 环境变量。此变量的值应为您的 Ably 公钥。您的公钥是 Ably 密钥中 : 字符之前的部分。

根据您的需要调整 Echo 配置后,您可以编译应用程序的资源

1npm run dev

要了解有关编译应用程序 JavaScript 资源的更多信息,请查阅有关 Vite 的文档。

概念概述

Laravel 的事件广播允许您使用基于驱动程序的方法将服务器端 Laravel 事件广播到客户端 JavaScript 应用程序。目前,Laravel 附带 Pusher ChannelsAbly 驱动程序。可以使用 Laravel Echo JavaScript 包在客户端轻松使用这些事件。

事件通过“频道”广播,频道可以指定为公共频道或私有频道。任何访问您应用程序的访问者都可以订阅公共频道,而无需任何身份验证或授权;但是,为了订阅私有频道,用户必须经过身份验证并被授权在该频道上监听。

使用示例应用程序

在深入研究事件广播的每个组件之前,让我们以一个电子商务商店为例进行高级概述。

在我们的应用程序中,假设我们有一个页面,允许用户查看其订单的运输状态。我们还假设当应用程序处理运输状态更新时,会触发 OrderShipmentStatusUpdated 事件

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

ShouldBroadcast 接口

当用户查看其订单之一时,我们不希望他们必须刷新页面才能查看状态更新。相反,我们希望在创建更新时将更新广播到应用程序。因此,我们需要使用 ShouldBroadcast 接口标记 OrderShipmentStatusUpdated 事件。这将指示 Laravel 在事件触发时广播该事件

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

ShouldBroadcast 接口要求我们的事件定义 broadcastOn 方法。此方法负责返回事件应广播到的频道。生成的事件类上已定义此方法的空存根,因此我们只需要填写其详细信息即可。我们只希望订单的创建者能够查看状态更新,因此我们将事件广播到与订单关联的私有频道

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

如果您希望事件在多个频道上广播,则可以返回一个 array

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

授权频道

请记住,用户必须被授权才能在私有频道上监听。我们可以在应用程序的 routes/channels.php 文件中定义我们的频道授权规则。在本例中,我们需要验证尝试在私有 orders.1 频道上监听的任何用户实际上是否是订单的创建者

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

channel 方法接受两个参数:频道名称和一个回调,该回调返回 truefalse,指示用户是否被授权在该频道上监听。

所有授权回调都将当前经过身份验证的用户作为其第一个参数,并将任何其他通配符参数作为其后续参数接收。在本例中,我们使用 {orderId} 占位符来指示频道名称的“ID”部分是通配符。

监听事件广播

接下来,剩下的就是在我们的 JavaScript 应用程序中监听事件。我们可以使用 Laravel Echo 来做到这一点。首先,我们将使用 private 方法订阅私有频道。然后,我们可以使用 listen 方法来监听 OrderShipmentStatusUpdated 事件。默认情况下,所有事件的公共属性都将包含在广播事件中

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

定义广播事件

要告知 Laravel 应该广播给定的事件,您必须在事件类上实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast 接口。此接口已导入到框架生成的所有事件类中,因此您可以轻松地将其添加到任何事件中。

ShouldBroadcast 接口要求您实现一个方法:broadcastOnbroadcastOn 方法应返回事件应广播到的频道或频道数组。频道应该是 ChannelPrivateChannelPresenceChannel 的实例。Channel 的实例表示任何用户都可以订阅的公共频道,而 PrivateChannelsPresenceChannels 表示需要频道授权的私有频道

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

实现 ShouldBroadcast 接口后,您只需像往常一样触发事件。一旦事件被触发,队列任务将自动使用您指定的广播驱动程序广播该事件。

广播名称

默认情况下,Laravel 将使用事件的类名广播事件。但是,您可以通过在事件上定义 broadcastAs 方法来自定义广播名称

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

如果您使用 broadcastAs 方法自定义广播名称,则应确保使用前导 . 字符注册您的监听器。这将指示 Echo 不要将应用程序的命名空间添加到事件

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

广播数据

广播事件时,其所有 public 属性都会自动序列化并作为事件的有效负载广播,从而允许您从 JavaScript 应用程序访问其任何公共数据。因此,例如,如果您的事件具有包含 Eloquent 模型的单个公共 $user 属性,则事件的广播有效负载将是

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

但是,如果您希望更精细地控制广播有效负载,则可以向事件添加 broadcastWith 方法。此方法应返回您希望作为事件有效负载广播的数据数组

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

广播队列

默认情况下,每个广播事件都放置在 queue.php 配置文件中指定的默认队列连接的默认队列中。您可以通过在事件类上定义 connectionqueue 属性来自定义广播器使用的队列连接和名称

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

或者,您可以通过在事件上定义 broadcastQueue 方法来自定义队列名称

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

如果您想使用 sync 队列而不是默认队列驱动程序广播事件,则可以实现 ShouldBroadcastNow 接口而不是 ShouldBroadcast

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

广播条件

有时,您只想在给定条件为真时广播事件。您可以通过向事件类添加 broadcastWhen 方法来定义这些条件

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

广播和数据库事务

当广播事件在数据库事务中分派时,它们可能会在数据库事务提交之前由队列处理。发生这种情况时,您在数据库事务期间对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,事务中创建的任何模型或数据库记录可能在数据库中不存在。如果您的事件依赖于这些模型,则在处理广播事件的作业时可能会发生意外错误。

如果您的队列连接的 after_commit 配置选项设置为 false,您仍然可以指示特定的广播事件应在所有打开的数据库事务都已提交后分派,方法是在事件类上实现 ShouldDispatchAfterCommit 接口

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

要了解有关解决这些问题的更多信息,请查看有关队列任务和数据库事务的文档。

授权频道

私有频道要求您授权当前经过身份验证的用户实际上可以在该频道上监听。这是通过向您的 Laravel 应用程序发出 HTTP 请求并提供频道名称,并允许您的应用程序确定用户是否可以在该频道上监听来完成的。当使用 Laravel Echo 时,将自动发出 HTTP 请求以授权订阅私有频道。

启用广播后,Laravel 会自动注册 /broadcasting/auth 路由以处理授权请求。/broadcasting/auth 路由会自动放置在 web 中间件组中。

定义授权回调

接下来,我们需要定义逻辑,该逻辑将实际确定当前经过身份验证的用户是否可以监听给定的频道。这在 install:broadcasting Artisan 命令创建的 routes/channels.php 文件中完成。在此文件中,您可以使用 Broadcast::channel 方法注册频道授权回调

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

channel 方法接受两个参数:频道名称和一个回调,该回调返回 truefalse,指示用户是否被授权在该频道上监听。

所有授权回调都将当前经过身份验证的用户作为其第一个参数,并将任何其他通配符参数作为其后续参数接收。在本例中,我们使用 {orderId} 占位符来指示频道名称的“ID”部分是通配符。

您可以使用 channel:list Artisan 命令查看应用程序的广播授权回调列表

1php artisan channel:list

授权回调模型绑定

与 HTTP 路由一样,频道路由也可以利用隐式和显式路由模型绑定。例如,您可以请求实际的 Order 模型实例,而不是接收字符串或数字订单 ID

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

与 HTTP 路由模型绑定不同,频道模型绑定不支持自动隐式模型绑定作用域。但是,这很少成为问题,因为大多数频道都可以根据单个模型的唯一主键进行作用域限定。

授权回调身份验证

私有和 Presence 广播频道通过应用程序的默认身份验证守卫对当前用户进行身份验证。如果用户未通过身份验证,则会自动拒绝频道授权,并且永远不会执行授权回调。但是,您可以分配多个自定义守卫,这些守卫应在必要时对传入请求进行身份验证

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

定义频道类

如果您的应用程序正在使用许多不同的频道,则您的 routes/channels.php 文件可能会变得臃肿。因此,您可以改用频道类而不是使用闭包来授权频道。要生成频道类,请使用 make:channel Artisan 命令。此命令会将新的频道类放置在 App/Broadcasting 目录中。

1php artisan make:channel OrderChannel

接下来,在您的 routes/channels.php 文件中注册您的频道

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

最后,您可以将频道的授权逻辑放在频道类的 join 方法中。此 join 方法将包含您通常放在频道授权闭包中的相同逻辑。您还可以利用频道模型绑定

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

与 Laravel 中的许多其他类一样,频道类将由服务容器自动解析。因此,您可以在其构造函数中类型提示频道所需的任何依赖项。

广播事件

定义事件并使用 ShouldBroadcast 接口标记它后,您只需使用事件的 dispatch 方法触发该事件即可。事件分发器将注意到事件已使用 ShouldBroadcast 接口标记,并将事件排队以进行广播

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

仅对其他人广播

在构建利用事件广播的应用程序时,您可能偶尔需要向给定频道的所有订阅者广播事件,但当前用户除外。您可以使用 broadcast 助手函数和 toOthers 方法来完成此操作

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

为了更好地理解何时可能要使用 toOthers 方法,让我们想象一个任务列表应用程序,用户可以通过输入任务名称来创建新任务。要创建任务,您的应用程序可能会向 /task URL 发出请求,该 URL 广播任务的创建并返回新任务的 JSON 表示形式。当您的 JavaScript 应用程序收到来自端点的响应时,它可能会像这样直接将新任务插入其任务列表中

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

但是,请记住,我们还广播了任务的创建。如果您的 JavaScript 应用程序也在监听此事件以便将任务添加到任务列表中,则您的列表中将有重复的任务:一个来自端点,另一个来自广播。您可以使用 toOthers 方法来解决此问题,以指示广播器不要向当前用户广播事件。

您的事件必须使用 Illuminate\Broadcasting\InteractsWithSockets trait 才能调用 toOthers 方法。

配置

当您初始化 Laravel Echo 实例时,会为连接分配一个套接字 ID。如果您使用全局 Axios 实例从 JavaScript 应用程序发出 HTTP 请求,则套接字 ID 将自动作为 X-Socket-ID 标头附加到每个传出请求。然后,当您调用 toOthers 方法时,Laravel 将从标头中提取套接字 ID,并指示广播器不要广播到任何具有该套接字 ID 的连接。

如果您未使用全局 Axios 实例,则需要手动配置 JavaScript 应用程序以使用所有传出请求发送 X-Socket-ID 标头。您可以使用 Echo.socketId 方法检索套接字 ID

1var socketId = Echo.socketId();

自定义连接

如果您的应用程序与多个广播连接交互,并且您希望使用非默认广播器广播事件,则可以使用 via 方法指定要将事件推送到哪个连接

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

或者,您可以通过在事件的构造函数中调用 broadcastVia 方法来指定事件的广播连接。但是,在执行此操作之前,您应确保事件类使用 InteractsWithBroadcasting trait

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

匿名事件

有时,您可能希望向应用程序的前端广播一个简单的事件,而无需创建专用的事件类。为了适应这种情况,Broadcast facade 允许您广播“匿名事件”

1Broadcast::on('orders.'.$order->id)->send();

上面的示例将广播以下事件

1{
2 "event": "AnonymousEvent",
3 "data": "[]",
4 "channel": "orders.1"
5}

使用 aswith 方法,您可以自定义事件的名称和数据

1Broadcast::on('orders.'.$order->id)
2 ->as('OrderPlaced')
3 ->with($order)
4 ->send();

上面的示例将广播如下事件

1{
2 "event": "OrderPlaced",
3 "data": "{ id: 1, total: 100 }",
4 "channel": "orders.1"
5}

如果您想在私有或 Presence 频道上广播匿名事件,则可以使用 privatepresence 方法

1Broadcast::private('orders.'.$order->id)->send();
2Broadcast::presence('channels.'.$channel->id)->send();

使用 send 方法广播匿名事件会将事件分派到应用程序的队列进行处理。但是,如果您想立即广播事件,则可以使用 sendNow 方法

1Broadcast::on('orders.'.$order->id)->sendNow();

要将事件广播到除当前经过身份验证的用户之外的所有频道订阅者,您可以调用 toOthers 方法

1Broadcast::on('orders.'.$order->id)
2 ->toOthers()
3 ->send();

接收广播

监听事件

一旦您安装并实例化 Laravel Echo,您就可以开始监听从 Laravel 应用程序广播的事件。首先,使用 channel 方法检索频道的实例,然后调用 listen 方法以监听指定的事件

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

如果您想在私有频道上监听事件,请改用 private 方法。您可以继续链接对 listen 方法的调用,以监听单个频道上的多个事件

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

停止监听事件

如果您想停止监听给定的事件而不离开频道,则可以使用 stopListening 方法

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

离开频道

要离开频道,您可以调用 Echo 实例上的 leaveChannel 方法

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

如果您想离开频道及其关联的私有频道和 Presence 频道,则可以调用 leave 方法

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

命名空间

您可能已经注意到在上面的示例中,我们没有为事件类指定完整的 App\Events 命名空间。这是因为 Echo 会自动假定事件位于 App\Events 命名空间中。但是,您可以通过传递 namespace 配置选项来实例化 Echo 时配置根命名空间

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

或者,您可以在使用 Echo 订阅事件类时,在事件类前加上 . 前缀。这将允许您始终指定完全限定的类名

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

Presence 频道

Presence 频道建立在私有频道的安全基础上,同时公开了谁订阅了该频道的额外感知功能。这使得构建强大的协作应用程序功能变得容易,例如在另一个用户正在查看同一页面时通知用户或列出聊天室的成员。

授权 Presence 频道

所有 presence 频道也是私有频道;因此,用户必须 被授权才能访问它们。但是,当为 presence 频道定义授权回调时,如果用户被授权加入频道,您将不会返回 true。相反,您应该返回一个关于用户数据的数组。

授权回调返回的数据将在您的 JavaScript 应用程序中提供给 presence 频道事件监听器。如果用户未被授权加入 presence 频道,您应该返回 falsenull

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

加入 Presence 频道

要加入 presence 频道,您可以使用 Echo 的 join 方法。join 方法将返回一个 PresenceChannel 实现,除了公开 listen 方法外,它还允许您订阅 herejoiningleaving 事件。

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

here 回调将在频道成功加入后立即执行,并将接收一个数组,其中包含当前订阅该频道的所有其他用户的用户信息。当新用户加入频道时,将执行 joining 方法,而当用户离开频道时,将执行 leaving 方法。当身份验证端点返回 HTTP 状态代码 200 以外的代码或解析返回的 JSON 时出现问题时,将执行 error 方法。

向 Presence 频道广播

Presence 频道可以像公共或私有频道一样接收事件。以聊天室为例,我们可能希望将 NewMessage 事件广播到房间的 presence 频道。为此,我们将从事件的 broadcastOn 方法返回一个 PresenceChannel 实例

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

与其他事件一样,您可以使用 broadcast 助手和 toOthers 方法将当前用户排除在接收广播之外

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

与其他类型的事件一样,您可以使用 Echo 的 listen 方法监听发送到 presence 频道的事件

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

模型广播

在阅读以下关于模型广播的文档之前,我们建议您熟悉 Laravel 模型广播服务的一般概念,以及如何手动创建和监听广播事件。

当您的应用程序的 Eloquent 模型 被创建、更新或删除时,广播事件是很常见的。当然,这可以通过手动 为 Eloquent 模型状态更改定义自定义事件 并使用 ShouldBroadcast 接口标记这些事件来轻松完成。

但是,如果您没有在应用程序中的任何其他目的中使用这些事件,那么为仅仅广播它们而创建事件类可能会很麻烦。为了解决这个问题,Laravel 允许您指示 Eloquent 模型应自动广播其状态更改。

要开始使用,您的 Eloquent 模型应使用 Illuminate\Database\Eloquent\BroadcastsEvents trait。此外,该模型应定义一个 broadcastOn 方法,该方法将返回模型事件应广播到的频道数组

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

一旦您的模型包含此 trait 并定义了其广播频道,它将开始在创建、更新、删除、软删除或恢复模型实例时自动广播事件。

此外,您可能已经注意到 broadcastOn 方法接收一个字符串 $event 参数。此参数包含模型上发生的事件类型,并将具有 createdupdateddeletedtrashedrestored 的值。通过检查此变量的值,您可以确定模型应广播到哪些频道(如果有)以进行特定事件

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

自定义模型广播事件创建

有时,您可能希望自定义 Laravel 如何创建底层模型广播事件。您可以通过在您的 Eloquent 模型上定义 newBroadcastableEvent 方法来实现这一点。此方法应返回一个 Illuminate\Database\Eloquent\BroadcastableModelEventOccurred 实例

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

模型广播约定

频道约定

您可能已经注意到,上面模型示例中的 broadcastOn 方法没有返回 Channel 实例。相反,直接返回了 Eloquent 模型。如果 Eloquent 模型实例由您模型的 broadcastOn 方法返回(或包含在方法返回的数组中),Laravel 将自动使用模型的类名和主键标识符作为频道名称来实例化模型的私有频道实例。

因此,id1App\Models\User 模型将被转换为名称为 App.Models.User.1Illuminate\Broadcasting\PrivateChannel 实例。当然,除了从模型的 broadcastOn 方法返回 Eloquent 模型实例之外,您还可以返回完整的 Channel 实例,以便完全控制模型的频道名称

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

如果您计划从模型的 broadcastOn 方法显式返回频道实例,您可以将 Eloquent 模型实例传递给频道的构造函数。这样做时,Laravel 将使用上面讨论的模型频道约定将 Eloquent 模型转换为频道名称字符串

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

如果您需要确定模型的频道名称,您可以对任何模型实例调用 broadcastChannel 方法。例如,对于 id1App\Models\User 模型,此方法返回字符串 App.Models.User.1

1$user->broadcastChannel()

事件约定

由于模型广播事件与应用程序的 App\Events 目录中的“实际”事件无关,因此它们根据约定被分配名称和有效负载。Laravel 的约定是使用模型的类名(不包括命名空间)和触发广播的模型事件的名称来广播事件。

因此,例如,对 App\Models\Post 模型的更新将向您的客户端应用程序广播一个名为 PostUpdated 的事件,并带有以下有效负载

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

删除 App\Models\User 模型将广播一个名为 UserDeleted 的事件。

如果您愿意,您可以通过向模型添加 broadcastAsbroadcastWith 方法来定义自定义广播名称和有效负载。这些方法接收模型事件/操作的名称,允许您为每个模型操作自定义事件的名称和有效负载。如果从 broadcastAs 方法返回 null,Laravel 将在广播事件时使用上面讨论的模型广播事件名称约定

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

监听模型广播

一旦您将 BroadcastsEvents trait 添加到您的模型并定义了模型的 broadcastOn 方法,您就可以开始在您的客户端应用程序中监听广播的模型事件了。在开始之前,您可能希望查阅关于 监听事件 的完整文档。

首先,使用 private 方法检索频道实例,然后调用 listen 方法以监听指定的事件。通常,提供给 private 方法的频道名称应与 Laravel 的 模型广播约定 相对应。

一旦您获得了频道实例,您就可以使用 listen 方法来监听特定事件。由于模型广播事件与应用程序的 App\Events 目录中的“实际”事件无关,因此 事件名称 必须以 . 为前缀,以表明它不属于特定命名空间。每个模型广播事件都有一个 model 属性,其中包含模型的所有可广播属性

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

客户端事件

当使用 Pusher Channels 时,您必须在您的 应用程序仪表板 的“App Settings”部分启用“Client Events”选项,才能发送客户端事件。

有时,您可能希望将事件广播到其他连接的客户端,而根本不访问您的 Laravel 应用程序。这对于诸如“正在输入”通知之类的功能特别有用,在这种情况下,您希望提醒您的应用程序用户,另一个用户正在给定屏幕上键入消息。

要广播客户端事件,您可以使用 Echo 的 whisper 方法

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

要监听客户端事件,您可以使用 listenForWhisper 方法

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

通知

通过将事件广播与 通知 配对,您的 JavaScript 应用程序可以在新通知发生时接收它们,而无需刷新页面。在开始之前,请务必阅读有关使用 广播通知频道 的文档。

一旦您配置了通知以使用广播频道,您可以使用 Echo 的 notification 方法监听广播事件。请记住,频道名称应与接收通知的实体的类名匹配

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

在此示例中,通过 broadcast 频道发送到 App\Models\User 实例的所有通知都将被回调接收。您的应用程序的 routes/channels.php 文件中包含 App.Models.User.{id} 频道的频道授权回调。

Laravel 是构建最高效的方式,用于
构建、部署和监控软件。