laravel
5. 深入探讨
广播

介绍

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

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

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

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

支持的驱动程序

默认情况下,Laravel 为您提供了三个服务器端广播驱动程序供您选择:Laravel Reverb (opens in a new tab)Pusher 频道 (opens in a new tab)Ably (opens in a new tab)

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

服务器端安装

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

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

配置

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

Laravel 开箱即支持多种广播驱动程序:Laravel ReverbPusher 频道 (opens in a new tab)Ably (opens in a new tab)以及用于本地开发和调试的log驱动程序。此外,还包含一个null驱动程序,允许您在测试期间禁用广播。config/broadcasting.php配置文件中为每个驱动程序都包含了一个配置示例。

安装

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

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 频道

如果您计划使用Pusher 频道 (opens in a new tab)广播您的事件,则应使用 Composer 包管理器安装 Pusher 频道 PHP SDK:

composer require pusher/pusher-php-server

接下来,您应该在config/broadcasting.php配置文件中配置您的 Pusher 频道凭据。此文件中已经包含了一个 Pusher 频道配置示例,使您可以快速指定您的密钥、机密和应用程序 ID。通常,您应该在应用程序的.env文件中配置您的 Pusher 频道凭据:

PUSHER_APP_ID="您的 Pusher 应用程序 ID"
PUSHER_APP_KEY="您的 Pusher 密钥"
PUSHER_APP_SECRET="您的 Pusher 机密"
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

[!注意]
下面的文档讨论了如何在“Pusher 兼容性”模式下使用 Ably。然而,Ably 团队推荐并维护了一个广播器和 Echo 客户端,能够利用 Ably 提供的独特功能。有关使用 Ably 维护的驱动程序的更多信息,请参考 Ably 的 Laravel 广播器文档 (opens in a new tab)

如果您计划使用Ably (opens in a new tab)广播您的事件,则应使用 Composer 包管理器安装 Ably PHP SDK:

composer require ably/ably-php

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

ABLY_KEY=您的 Ably 密钥

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

BROADCAST_CONNECTION=ably

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

客户端安装

Reverb

Laravel Echo (opens in a new tab)是一个 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,
    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 频道

Laravel Echo (opens in a new tab)是一个 JavaScript 库,使您可以轻松地订阅频道并监听由服务器端广播驱动程序广播的事件。Echo 还利用pusher-jsNPM 包来实现 WebSocket 订阅、频道和消息的 Pusher 协议。

install:broadcastingArtisan 命令会自动为您安装laravel-echopusher-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="您的 Pusher 应用程序 ID"
PUSHER_APP_KEY="您的 Pusher 密钥"
PUSHER_APP_SECRET="您的 Pusher 机密"
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 频道客户端实例,并且希望 Echo 能够使用它,您可以通过client配置选项将其传递给 Echo:

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
 
const options = {
    broadcaster: 'pusher',
    key: '您的 Pusher 频道密钥'
}
 
window.Echo = new Echo({
   ...options,
    client: new Pusher(options.key, options)
});

Ably

[!注意]
下面的文档讨论了如何在“Pusher 兼容性”模式下使用 Ably。然而,Ably 团队推荐并维护了一个广播器和 Echo 客户端,能够利用 Ably 提供的独特功能。有关使用 Ably 维护的驱动程序的更多信息,请参考 Ably 的 Laravel 广播器文档 (opens in a new tab)

Laravel Echo (opens in a new tab)是一个 JavaScript 库,使您可以轻松地订阅频道并监听由服务器端广播驱动程序广播的事件。Echo 还利用pusher-jsNPM 包来实现 WebSocket 订阅、频道和消息的 Pusher 协议。

install:broadcastingArtisan 命令会自动为您安装laravel-echopusher-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 频道 (opens in a new tab)Ably (opens in a new tab)驱动程序。可以使用Laravel EchoJavaScript 包在客户端轻松地使用这些事件。

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

使用示例应用程序

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

在我们的应用程序中,假设我们有一个页面,允许用户查看他们订单的发货状态。还假设当应用程序处理发货状态更新时,会触发一个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 方法接受两个参数:频道的名称和一个回调函数,该回调函数返回 truefalse,表示用户是否被授权在该频道上收听。

所有授权回调函数都将当前经过身份验证的用户作为其第一个参数,并将任何其他通配符参数作为后续参数接收。在这个例子中,我们使用 {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 接口要求您实现一个单一的方法:broadcastOnbroadcastOn 方法应该返回事件应该广播的一个频道或频道数组。频道应该是 ChannelPrivateChannelPresenceChannel 的实例。Channel 的实例表示任何用户都可以订阅的公共频道,而 PrivateChannelsPresenceChannels 表示需要 频道授权 的私有频道:

<?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 配置文件中指定的默认队列连接的默认队列上。您可以通过在事件类上定义 connectionqueue 属性来自定义广播器使用的队列连接和名称:

/**
 * 用于广播事件的队列连接的名称。
 *
 * @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;
}

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

授权频道

私有频道要求您授权当前经过身份验证的用户实际上可以在该频道上收听。这是通过向您的 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 方法接受两个参数:频道的名称和一个回调函数,该回调函数返回 truefalse,表示用户是否被授权在该频道上收听。

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

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

php artisan channel:list

授权回调模型绑定

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

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

[!警告]
与 HTTP 路由模型绑定不同,频道模型绑定不支持自动 隐式模型绑定范围。但是,这很少是一个问题,因为大多数频道可以根据单个模型的唯一主键进行范围界定。

授权回调身份验证

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

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

定义频道类

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

php artisan make:channel OrderChannel

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

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

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

<?php
 
namespace App\Broadcasting;
 
use App\Models\Order;
use App\Models\User;
 
class OrderChannel
{
    /**
     * 创建一个新的频道实例。
     */
    public function __construct() {}
 
    /**
     * 验证用户对频道的访问权限。
     */
    public function join(User $user, Order $order): array|bool
    {
        return $user->id === $order->user_id;
    }
}

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

广播事件

一旦您定义了一个事件并使用 ShouldBroadcast 接口对其进行了标记,您只需要使用事件的 dispatch 方法来触发事件。事件调度器会注意到该事件被标记为 ShouldBroadcast 接口,并将事件排队进行广播:

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

仅对其他人

在构建使用事件广播的应用程序时,您可能偶尔需要将事件广播到给定频道的所有订阅者,但不包括当前用户。您可以使用 broadcast 助手和 toOthers 方法来实现这一点:

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

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

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

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

[!警告]
您的事件必须使用 Illuminate\Broadcasting\InteractsWithSockets 特征才能调用 toOthers 方法。

配置

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

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

var socketId = Echo.socketId();

自定义连接

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

use App\Events\OrderShipmentStatusUpdated;
 
broadcast(new OrderShipmentStatus
### 匿名事件
 
有时,您可能希望向应用程序的前端广播一个简单的事件,而无需创建专用的事件类。为了满足这一需求,`Broadcast`外观允许您广播“匿名事件”:
 
```php
Broadcast::on('orders.'.$order->id)->send();

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

{
    "event": "AnonymousEvent",
    "data": "[]",
    "channel": "orders.1"
}

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

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

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

{
    "event": "OrderPlaced",
    "data": "{ id: 1, total: 100 }",
    "channel": "orders.1"
}

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

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 应用程序中提供给存在频道事件监听器。如果用户未被授权加入存在频道,您应该返回falsenull

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方法外,还允许您订阅herejoiningleaving事件。

Echo.join(`chat.${roomId}`)
   .here((users) => {
        //...
    })
   .joining((user) => {
        console.log(user.name);
    })
   .leaving((user) => {
        console.log(user.name);
    })
   .error((error) => {
        console.error(error);
    });

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

向存在频道广播

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

/**
 * 获取事件应广播的频道。
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new PresenceChannel('chat.'.$this->message->room_id),
    ];
}

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

broadcast(new NewMessage($message));

broadcast(new NewMessage($message))->toOthers();

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

Echo.join(`chat.${roomId}`)
   .here(/*... */)
   .joining(/*... */)
   .leaving(/*... */)
   .listen('NewMessage', (e) => {
        //...
    });

模型广播

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

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

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

首先,您的 Eloquent 模型应该使用Illuminate\Database\Eloquent\BroadcastsEvents特征。此外,模型应该定义一个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];
    }
}

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

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

/**
 * 获取模型事件应广播的频道。
 *
 * @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 将使用模型的类名和主键标识符作为频道名称自动为该模型实例化一个私有频道实例。

因此,一个id1App\Models\User模型将被转换为一个名称为App.Models.User.1Illuminate\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方法。例如,对于一个id1App\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的事件。

如果您愿意,可以通过在模型中添加broadcastAsbroadcastWith方法来定义自定义广播名称和有效负载。这些方法接收正在发生的模型事件/操作的名称,允许您为每个模型操作自定义事件的名称和有效负载。如果从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 (opens in a new tab)时,您必须在应用程序仪表板 (opens in a new tab)的“应用设置”部分中启用“客户端事件”选项,才能发送客户端事件。

有时,您可能希望在完全不触及 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文件中。