广播
介绍
在许多现代 Web 应用程序中,WebSockets 用于实现实时、实时更新的用户界面。当服务器上的某些数据更新时,通常会通过 WebSocket 连接发送消息以由客户端处理。WebSockets 提供了一种更高效的替代方案,以不断轮询应用程序的服务器以获取应在 UI 中反映的数据更改。
例如,假设您的应用程序能够将用户的数据导出到 CSV 文件并通过电子邮件发送给他们。但是,创建此 CSV 文件需要几分钟时间,因此您选择在队列作业中创建和发送 CSV。当 CSV 已创建并发送给用户时,我们可以使用事件广播来调度 App\Events\UserDataExported
事件,该事件由我们应用程序的 JavaScript 接收。一旦收到事件,我们就可以向用户显示一条消息,告知他们的 CSV 已通过电子邮件发送给他们,而无需他们刷新页面。
为了帮助您构建这些类型的功能,Laravel 使您可以轻松地通过 WebSocket 连接“广播”服务器端 Laravel 事件。广播您的 Laravel 事件允许您在服务器端 Laravel 应用程序和客户端 JavaScript 应用程序之间共享相同的事件名称和数据。
广播的核心概念很简单:客户端在前端连接到命名频道,而您的 Laravel 应用程序在后端将事件广播到这些频道。这些事件可以包含您希望提供给前端的任何附加数据。
支持的驱动程序
默认情况下,Laravel 包含三个服务器端广播驱动程序供您选择:Laravel Reverb、Pusher Channels 和 Ably。
NOTE
在深入了解事件广播之前,请确保您已阅读 Laravel 关于事件和监听器的文档。
服务器端安装
要开始使用 Laravel 的事件广播,我们需要在 Laravel 应用程序中进行一些配置并安装一些软件包。
事件广播是通过服务器端广播驱动程序完成的,该驱动程序广播您的 Laravel 事件,以便 Laravel Echo(一个 JavaScript 库)可以在浏览器客户端中接收它们。别担心——我们将逐步介绍安装过程的每个部分。
配置
所有应用程序的事件广播配置都存储在 config/broadcasting.php
配置文件中。如果您的应用程序中不存在此文件,请不要担心;当您运行 install:broadcasting
Artisan 命令时,它将被创建。
Laravel 开箱即支持多种广播驱动程序:Laravel Reverb、Pusher Channels、Ably 和用于本地开发和调试的 log
驱动程序。此外,还包括一个 null
驱动程序,允许您在测试期间禁用广播。config/broadcasting.php
配置文件中包含了每个驱动程序的配置示例。
安装
默认情况下,广播在新的 Laravel 应用程序中未启用。您可以使用 install:broadcasting
Artisan 命令启用广播:
php artisan install:broadcasting
install:broadcasting
命令将创建 config/broadcasting.php
配置文件。此外,该命令将创建 routes/channels.php
文件,您可以在其中注册应用程序的广播授权路由和回调。
队列配置
在广播任何事件之前,您应该首先配置并运行队列工作者。所有事件广播都是通过队列作业完成的,因此应用程序的响应时间不会因事件广播而受到严重影响。
Reverb
运行 install:broadcasting
命令时,系统会提示您安装 Laravel Reverb。当然,您也可以使用 Composer 包管理器手动安装 Reverb。
composer require laravel/reverb
安装软件包后,您可以运行 Reverb 的安装命令以发布配置、添加 Reverb 所需的环境变量并在应用程序中启用事件广播:
php artisan reverb:install
您可以在 Reverb 文档中找到详细的 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 支持的其他 options
,例如集群。
然后,在应用程序的 .env
文件中将 BROADCAST_CONNECTION
环境变量设置为 pusher
:
BROADCAST_CONNECTION=pusher
最后,您已准备好安装和配置 Laravel Echo,它将在客户端接收广播事件。
Ably
NOTE
下面的文档讨论了如何在“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 配置 - 您只需取消注释并将 broadcaster
配置选项更新为 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 ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
接下来,您应该编译应用程序的资产:
npm run build
WARNING
Laravel Echo reverb
广播器需要 laravel-echo v1.16.0+。
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
NOTE
要了解有关编译应用程序的 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
NOTE
下面的文档讨论了如何在“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
NOTE
要了解有关编译应用程序的 JavaScript 资产的更多信息,请查阅有关 Vite 的文档。
概念概述
Laravel 的事件广播允许您使用基于驱动程序的方法通过 WebSockets 将服务器端 Laravel 事件广播到客户端 JavaScript 应用程序。目前,Laravel 附带 Pusher Channels 和 Ably 驱动程序。事件可以使用 Laravel Echo JavaScript 包轻松地在客户端消费。
事件通过“频道”广播,这些频道可以指定为公共或私有。应用程序的任何访问者都可以在没有任何身份验证或授权的情况下订阅公共频道;然而,为了订阅私有频道,用户必须经过身份验证并被授权收听该频道。
使用示例应用程序
在深入了解事件广播的每个组件之前,让我们以电子商务商店为例进行高层次概述。
在我们的应用程序中,假设我们有一个页面允许用户查看其订单的运输状态。假设在应用程序处理运输状态更新时会触发 OrderShipmentStatusUpdated
事件:
use App\Events\OrderShipmentStatusUpdated;
OrderShipmentStatusUpdated::dispatch($order);
ShouldBroadcast
接口
当用户查看其订单之一时,我们不希望他们必须刷新页面以查看状态更新。相反,我们希望在创建更新时将其广播到应用程序。因此,我们需要使用 ShouldBroadcast
接口标记 OrderShipmentStatusUpdated
事件。这将指示 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 来做到这一点。首先,我们将使用 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
的实例表示任何用户都可以订阅的公共频道,而 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 应用程序访问其任何公共数据。因此,例如,如果您的事件有一个包含 Eloquent 模型的公共 $user
属性,事件的广播有效负载将是:
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}
但是,如果您希望对广播有效负载进行更细粒度的控制,您可以在事件中添加一个 broadcastWith
方法。此方法应返回您希望作为事件有效负载广播的数据数组:
/**
* 获取要广播的数据。
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return ['id' => $this->user->id];
}
广播队列
默认情况下,每个广播事件都放置在 queue.php
配置文件中指定的默认队列连接的默认队列上。您可以通过在事件类上定义 connection
和 queue
属性来自定义广播器使用的队列连接和名称:
/**
* 广播事件时要使用的队列连接的名称。
*
* @var string
*/
public $connection = 'redis';
/**
* 要放置广播作业的队列的名称。
*
* @var string
*/
public $queue = 'default';
或者,您可以通过在事件上定义 broadcastQueue
方法来自定义队列名称:
/**
* 要放置广播作业的队列的名称。
*/
public function broadcastQueue(): string
{
return 'default';
}
如果您希望使用 sync
队列而不是默认队列驱动程序广播事件,您可以实现 ShouldBroadcastNow
接口而不是 ShouldBroadcast
:
<?php
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;
}
NOTE
要了解有关解决这些问题的更多信息,请查看有关队列作业和数据库事务的文档。
授权频道
私有频道要求您授权当前经过身份验证的用户是否可以实际收听该频道。这是通过向您的 Laravel 应用程序发送带有频道名称的 HTTP 请求并允许您的应用程序确定用户是否可以收听该频道来实现的。当使用 Laravel Echo 时,授权订阅私有频道的 HTTP 请求将自动发出。
启用广播时,Laravel 会自动注册 /broadcasting/auth
路由以处理授权请求。/broadcasting/auth
路由会自动放置在 web
中间件组中。
定义授权回调
接下来,我们需要定义实际确定当前经过身份验证的用户是否可以收听给定频道的逻辑。这是在 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;
});
WARNING
与 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;
}
}
NOTE
像 Laravel 中的许多其他类一样,频道类将自动由服务容器解析。因此,您可以在其构造函数中类型提示频道所需的任何依赖项。
广播事件
一旦您定义了一个事件并使用 ShouldBroadcast
接口标记它,您只需使用事件的调度方法触发事件。事件调度器会注意到事件标记为 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
方法来解决此问题,以指示广播器不要将事件广播给当前用户。
WARNING
您的事件必须使用 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
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
和 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(/* ... */);
停止监听事件
如果您希望停止监听给定事件而不离开频道,您可以使用 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) => {
// ...
});
存在频道
存在频道建立在私有频道的安全性之上,同时提供了了解谁订阅了频道的附加功能。这使得构建强大的协作应用程序功能变得容易,例如在另一个用户查看同一页面时通知用户或列出聊天室的成员。
授权存在频道
所有存在频道也是私有频道;因此,用户必须被授权访问它们。但是,在为存在频道定义授权回调时,如果用户被授权加入频道,您将不会返回 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) => {
// ...
});
模型广播
WARNING
在阅读以下有关模型广播的文档之前,我们建议您熟悉 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('.PostUpdated', (e) => {
console.log(e.model);
});
客户端事件
NOTE
使用 Pusher Channels 时,您必须在应用程序仪表板的“应用程序设置”部分中启用“客户端事件”选项,以便发送客户端事件。
有时,您可能希望将事件广播到其他连接的客户端,而无需访问您的 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);
});
在此示例中,通过 broadcast
频道发送到 App\Models\User
实例的所有通知都将由回调接收。App.Models.User.{id}
频道的频道授权回调包含在应用程序的 routes/channels.php
文件中。