广播系统

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

广播系统

介绍

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

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

为了帮助你构建此类功能,Laravel 让通过 WebSocket 连接来「广播」你的服务器端 Laravel 事件 变得容易。对你的 Laravel 事件进行广播,使你能够在服务器端的 Laravel 应用程序与客户端的 JavaScript 应用程序之间共享相同的事件名称和数据。

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

支持的驱动程序

默认情况下,Laravel包含了三个服务器端广播驱动程序供你选择:Laravel ReverbPusher Channels,和 Ably

注意
在深入了解事件广播之前,请确保你已阅读了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 命令来启用广播:

php artisan install:broadcasting

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

队列配置

在对任何事件进行广播之前,你应该首先配置并运行一个队列。所有的事件广播均通过队列来实现,以确保你的应用程序的响应时间不会受到广播事件的影响。

Reverb

当运行 install:broadcasting 命令时,你会收到安装 Laravel Reverb 的提示。当然,您也可以通过 Composer 包管理器手动安装 Reverb 。由于 Reverb 当下处于测试阶段,您需要专门安装测试版:

composer require laravel/reverb:@beta

软件包安装完成后,你可以运行 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 的示例配置,让你可以快速指定 key 、 secret 和 应用 ID 。通常,你应该在应用的 .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"

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

接下来,你需要在你的 .env 文件中将 BROADCAST_CONNECTION 更改为 pusher

BROADCAST_CONNECTION=pusher

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

Ably

注意
下面的文档介绍了如何在 「Pusher 兼容」 模式下使用 Ably 。然而,Ably 团队推荐并维护了一个能够利用 Ably 独特功能的广播器和 Echo 客户端。 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 在 WebSocket 订阅,频道和消息方面使用了 Pusher 协议。

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 Channels

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

install:broadcasting Artisan 命令会自动为你安装 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="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

使用现有客户端实例

如果你希望 Echo 使用一个你已经进行了预配置的 Pusher Channels 客户端实例,你可以通过 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 团队推荐并维护了一个能够利用 Ably 独特功能的广播器和 Echo 客户端。 Ably 维护的驱动程序的更多使用信息,请 参考 Ably 的 Laravel 广播文档

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

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 的事件广播允许你使用基于驱动程序的 WebSocket 方法,将服务器端 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接口要求你实现一个单一的方法:broadcastOnbroadcastOn 方法应该返回一个频道或者一个频道的数组,事件应该在这些频道上被广播。这些频道应该是 ChannelPrivateChannel, 或 PresenceChannel 的实例。Channel 的实例代表任何用户都可以订阅的公共频道,而 PrivateChannels 和 PresenceChannels 代表需要 频道授权 的私有频道:

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    /**
     * 创建一个新的事件实例。
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * 获取事件应该广播到哪些频道。
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('user.'.$this->user->id),
        ];
    }
}

在实现了 ShouldBroadcast 接口之后,你只需要像平常一样 触发事件。一旦事件被触发,一个 队列任务 将自动使用你指定的广播驱动来广播事件。

广播名称

默认情况下,Laravel 会使用事件的类名进行广播。但是,你可以通过在事件中定义一个 broadcastAs 方法来自定义广播名称:

/**
 * 事件的广播名字
 */
public function broadcastAs(): string
{
    return 'server.created';
}

如果你使用 broadcastAs 方法自定义了广播名称,你应该确保你的监听器是以一个前导 . 字符注册的。这可以通知 Echo 不要将应用的命名空间添加到事件前面:

.listen('.server.created', function (e) {
    ....
});

广播数据

当一个事件被广播时,所有的 public 属性会被自动序列化并作为事件负载进行广播,允许你从你的 JavaScript 应用中访问它的任何公共数据。例如,如果你的事件有一个公共的 $user 属性,它包含一个 Eloquent 模型,那么事件的广播负载将会是:

{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}

然而,如果你希望对你的广播负载有更细粒度的控制,你可以在你的事件中添加一个 broadcastWith 方法。这个方法应该返回一个数组,数组中包含你希望作为事件负载进行广播的数据:

/**
 * 获取将要广播的数据
 *
 * @return array<string, mixed>
 */
public function broadcastWith(): array
{
    return ['id' => $this->user->id];
}

广播队列

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

/**
 * 当广播事件时使用的队列连接的名字
 *
 * @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 中间件组中。

定义授权回调

接下来,我们需要定义实际决定当前认证用户的逻辑,是否可以监听给定频道。这是在 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 路由模型绑定,通道模型绑定不支持自动隐式模型绑定范围。然而,这通常不是问题,因为大多数通道可以基于单个模型的唯一主键进行作用域限定。

授权回调验证

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

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 方法来指示广播器不向当前用户广播事件来解决这个问题。

注意
要调用 toOthers 方法,你的事件必须使用 Illuminate\Broadcasting\InteractsWithSockets 特性。

配置

当你初始化一个 Laravel Echo 实例时,一个套接字 ID 将被分配给链接。如果你正在使用全局 Axios 实例从你的 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 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) => {
        // ...
    });

Presence 频道

Presence 频道基于私有频道的安全性,同时暴露了关于谁订阅了频道的额外功能。这使得构建强大的,协作的应用程序功能变得容易,例如当另一个用户正在浏览同一页面时通知用户或者列出聊天室的成员。

授权存在频道

所有的存在频道也都是私有频道;因此,用户必须被授权访问他们。然而,当为 存在频道定义授权回调时,如果用户被授权加入频道,你将不会返回 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 方法,允许你订阅 herejoining 和 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) => {
        // ...
    });

模型广播

注意
在阅读以下关于模型广播的文档之前,我们建议你熟悉 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];
    }
}

一旦你的模型引入了这个特质并定义了其广播频道,它就会在模型实例被创建、更新、删除、移到回收站或还原时开始自动广播事件。

另外,你可能注意到 broadcastOn 方法接收了一个字符串 $event 参数。这个参数包含了模型发生的事件类型并将具有 createdupdateddeletedtrashed 或 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 模型实例(或者该方法返回的数组中包含了一个 Eloquent 模型实例),Laravel 将自动为模型实例化一个私有频道,使用模型的类名和主键标识符作为频道名称。

所以,一个 id 为 1 的 App\Models\User 模型将会被转换为一个名称为 App.Models.User.1 的 Illuminate\Broadcasting\PrivateChannel 实例。当然,你可以在你的模型 broadcastOn 方法中返回完整的 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);
    });

客户端事件

注意
使用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 实例的通知会被回调得到。应用的 routes/channels.php 文件中已经包含了 App.Models.User.{id} 频道的授权回调。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/11.x/br...

译文地址:https://learnku.com/docs/laravel/11.x/br...

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
贡献者:6
讨论数量: 0
发起讨论 只看当前版本


暂无话题~