Lzh on GitHub

引言

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

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

为了帮助您构建这些类型的功能,Laravel 使将服务器端 Laravel 事件 “广播”到 WebSocket 连接变得容易。广播您的 Laravel 事件允许您在服务器端 Laravel 应用程序和客户端 JavaScript 应用程序之间共享相同的事件名称和数据。

广播的核心概念很简单:客户端在前端连接到命名的通道,而您的 Laravel 应用程序在后端将事件广播到这些通道。这些事件可以包含您希望在前端可用的任何附加数据。

支持的驱动程序

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

在深入研究事件广播之前,请务必阅读 Laravel 关于 事件和侦听器 的文档。

快速入门

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

php artisan install:broadcasting

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

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

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

下一步

启用事件广播后,您就可以了解更多关于 定义广播事件侦听事件 的信息了。如果您正在使用 Laravel 的 React 或 Vue 入门工具包,您可以使用 Echo 的 useEcho 钩子 来侦听事件。

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

服务器端安装

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

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

Reverb

要在使用 Reverb 作为事件广播器时快速启用对 Laravel 广播功能的支持,请使用 --reverb 选项调用 install:broadcasting Artisan 命令。此 Artisan 命令将安装 Reverb 所需的 Composer 和 NPM 包,并使用适当的变量更新您的应用程序的 .env 文件:

php artisan install:broadcasting --reverb

手动安装

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

composer require laravel/reverb

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

php artisan reverb:install

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

Pusher Channels

要在使用 Pusher 作为事件广播器时快速启用对 Laravel 广播功能的支持,请使用 --pusher 选项调用 install:broadcasting Artisan 命令。此 Artisan 命令将提示您输入 Pusher 凭据,安装 Pusher PHP 和 JavaScript SDK,并使用适当的变量更新您的应用程序的 .env 文件:

php artisan install:broadcasting --pusher

手动安装

要手动安装 Pusher 支持,您应该使用 Composer 包管理器安装 Pusher Channels PHP SDK:

composer require pusher/pusher-php-server

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

PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"

config/broadcasting.php 文件的 pusher 配置还允许您指定 Channels 支持的附加选项,例如集群。

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

BROADCAST_CONNECTION=pusher

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

Ably

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

要在使用 Ably 作为事件广播器时快速启用对 Laravel 广播功能的支持,请使用 --ably 选项调用 install:broadcasting Artisan 命令。此 Artisan 命令将提示您输入 Ably 凭据,安装 Ably PHP 和 JavaScript SDK,并使用适当的变量更新您的应用程序的 .env 文件:

php artisan install:broadcasting --ably

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

手动安装

要手动安装 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 库,它使得订阅通道和侦听由服务器端广播驱动程序广播的事件变得轻松。

当通过 install:broadcasting Artisan 命令安装 Laravel Reverb 时,Reverb 和 Echo 的脚手架和配置将自动注入到您的应用程序中。但是,如果您希望手动配置 Laravel Echo,您可以按照下面的说明进行操作。

手动安装

要为应用程序的前端手动配置 Laravel Echo,首先安装 pusher-js 包,因为 Reverb 使用 Pusher 协议进行 WebSocket 订阅、通道和消息:

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

安装 Echo 后,您就可以在应用程序的 JavaScript 中创建一个新的 Echo 实例了。一个很好的位置是在 Laravel 框架附带的 resources/js/bootstrap.js 文件底部:

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
Laravel Echo reverb 广播器需要 laravel-echo v1.16.0+。

Pusher Channels

Laravel Echo 是一个 JavaScript 库,它使得订阅通道和侦听由服务器端广播驱动程序广播的事件变得轻松。

当通过 install:broadcasting --pusher Artisan 命令安装广播支持时,Pusher 和 Echo 的脚手架和配置将自动注入到您的应用程序中。但是,如果您希望手动配置 Laravel Echo,您可以按照下面的说明进行操作。

手动安装

要为应用程序的前端手动配置 Laravel Echo,首先安装 laravel-echopusher-js 包,它们使用 Pusher 协议进行 WebSocket 订阅、通道和消息:

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

安装 Echo 后,您就可以在应用程序的 resources/js/bootstrap.js 文件中创建一个新的 Echo 实例了:

import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    forceTLS: true
});

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

PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"

VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

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

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

使用现有客户端实例

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

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

const options = {
  broadcaster: 'pusher',
  key: import.meta.env.VITE_PUSHER_APP_KEY
}

window.Echo = new Echo({
  ...options,
  client: new Pusher(options.key, options)
});

Ably

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

Laravel Echo 是一个 JavaScript 库,它使得订阅通道和侦听由服务器端广播驱动程序广播的事件变得轻松。

当通过 install:broadcasting --ably Artisan 命令安装广播支持时,Ably 和 Echo 的脚手架和配置将自动注入到您的应用程序中。但是,如果您希望手动配置 Laravel Echo,您可以按照下面的说明进行操作。

手动安装

要为应用程序的前端手动配置 Laravel Echo,首先安装 laravel-echopusher-js 包,它们使用 Pusher 协议进行 WebSocket 订阅、通道和消息:

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

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

安装 Echo 后,您就可以在应用程序的 resources/js/bootstrap.js 文件中创建一个新的 Echo 实例了:

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 的事件广播允许您使用基于驱动程序的方法将服务器端 Laravel 事件广播到客户端 JavaScript 应用程序。目前,Laravel 附带 Laravel ReverbPusher ChannelsAbly 驱动程序。可以使用 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
{
    /**
     * The order instance.
     *
     * @var \App\Models\Order
     */
    public $order;
}

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

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channel the event should broadcast on.
 */
public function broadcastOn(): Channel
{
    return new PrivateChannel('orders.'.$this->order->id);
}

如果您希望事件广播到多个通道,您可以改为返回一个数组

use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new PrivateChannel('orders.'.$this->order->id),
        // ...
    ];
}

授权通道

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

use App\Models\Order;
use App\Models\User;

Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

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

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

侦听事件广播

接下来,剩下的就是在我们的 JavaScript 应用程序中侦听事件。我们可以使用 Laravel Echo 来完成此操作。Laravel Echo 的内置 React 和 Vue 钩子使其入门变得简单,并且默认情况下,事件的所有公共属性都将包含在广播事件中:

import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "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;

    /**
     * Create a new event instance.
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('user.'.$this->user->id),
        ];
    }
}

实现 ShouldBroadcast 接口后,您只需像往常一样 触发事件 即可。事件触发后,排队作业 将自动使用您指定的广播驱动程序广播事件。

广播名称

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

/**
 * The event's broadcast name.
 */
public function broadcastAs(): string
{
    return 'server.created';
}

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

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

广播数据

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

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

但是,如果您希望对广播负载进行更精细的控制,您可以向事件中添加一个 broadcastWith 方法。此方法应返回您希望作为事件负载广播的数据数组:

/**
 * Get the data to broadcast.
 *
 * @return array<string, mixed>
 */
public function broadcastWith(): array
{
    return ['id' => $this->user->id];
}

广播队列

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

/**
 * The name of the queue connection to use when broadcasting the event.
 *
 * @var string
 */
public $connection = 'redis';

/**
 * The name of the queue on which to place the broadcasting job.
 *
 * @var string
 */
public $queue = 'default';

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

/**
 * The name of the queue on which to place the broadcasting job.
 */
public function broadcastQueue(): string
{
    return 'default';
}

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

<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
    // ...
}

广播条件

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

/**
 * Determine if this event should broadcast.
 */
public function broadcastWhen(): bool
{
    return $this->order->value > 100;
}

广播和数据库事务

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

如果您的队列连接的 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
{
    /**
     * Create a new channel instance.
     */
    public function __construct() {}

    /**
     * Authenticate the user's access to the channel.
     */
    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 发出请求,该 URL 会广播任务的创建并返回新任务的 JSON 表示。当您的 JavaScript 应用程序从端点接收到响应时,它可能会像这样将新任务直接插入其任务列表:

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

但是,请记住我们还广播了任务的创建。如果您的 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:

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;

    /**
     * Create a new event instance.
     */
    public function __construct()
    {
        $this->broadcastVia('pusher');
    }
}

匿名事件

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

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 在广播事件时遇到错误时,会抛出异常,这通常会导致最终用户看到应用程序错误。由于事件广播通常是应用程序核心功能的补充,因此您可以通过在事件上实现 ShouldRescue 接口来防止这些异常中断用户体验。

实现 ShouldRescue 接口的事件在广播尝试期间会自动利用 Laravel 的 rescue 辅助函数。此辅助函数会捕获任何异常,将其报告给应用程序的异常处理程序以进行日志记录,并允许应用程序继续正常执行而不会中断用户的工作流程:

<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldRescue;

class ServerCreated implements ShouldBroadcast, ShouldRescue
{
    // ...
}

接收广播

侦听事件

一旦您 安装并实例化了 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) => {
        // ...
    });

使用 React 或 Vue

Laravel Echo 包含 React 和 Vue 钩子,使侦听事件变得轻松。要开始,请调用 useEcho 钩子,它用于侦听私有事件。useEcho 钩子将在使用组件卸载时自动离开通道:

<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>

您可以通过向 useEcho 提供一个事件数组来侦听多个事件:

useEcho(
    `orders.${orderId}`,
    ["OrderShipmentStatusUpdated", "OrderShipped"],
    (e) => {
        console.log(e.order);
    },
);

您还可以指定广播事件负载数据的形状,提供更高的类型安全性和编辑便利性:

type OrderData = {
    order: {
        id: number;
        user: {
            id: number;
            name: string;
        };
        created_at: string;
    };
};

useEcho<OrderData>(`orders.${orderId}`, "OrderShipmentStatusUpdated", (e) => {
    console.log(e.order.id);
    console.log(e.order.user.id);
});

useEcho 钩子将在使用组件卸载时自动离开通道;但是,您可以使用返回的函数在必要时以编程方式手动停止/开始侦听通道:

import { useEcho } from "@laravel/echo-react";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();

连接到公共通道

要连接到公共通道,您可以使用 useEchoPublic 钩子:

import { useEchoPublic } from "@laravel/echo-react";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});

连接到存在通道

要连接到存在通道,您可以使用 useEchoPresence 钩子:

import { useEchoPresence } from "@laravel/echo-react";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});

存在通道

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

授权存在通道

所有存在通道也是私有通道;因此,用户必须被 授权访问它们。但是,在为存在通道定义授权回调时,如果用户被授权加入通道,您将不会返回 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 方法。当身份验证端点返回除 200 之外的 HTTP 状态码或解析返回的 JSON 时出现问题时,将执行 error 方法。

广播到存在通道

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

/**
 * Get the channels the event should broadcast on.
 *
 * @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;

    /**
     * Get the user that the post belongs to.
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Get the channels that model events should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
     */
    public function broadcastOn(string $event): array
    {
        return [$this, $this->user];
    }
}

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

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

/**
 * Get the channels that model events should broadcast on.
 *
 * @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;

/**
 * Create a new broadcastable model event for the model.
 */
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;

/**
 * Get the channels that model events should broadcast on.
 *
 * @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 将在广播事件时使用上面讨论的模型广播事件名称约定:

/**
 * The model event's broadcast name.
 */
public function broadcastAs(string $event): string|null
{
    return match ($event) {
        'created' => 'post.created',
        default => null,
    };
}

/**
 * Get the data to broadcast for the model.
 *
 * @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('.UserUpdated', (e) => {
        console.log(e.model);
    });

使用 React 或 Vue

如果您正在使用 React 或 Vue,您可以使用 Laravel Echo 附带的 useEchoModel 钩子来轻松侦听模型广播:

import { useEchoModel } from "@laravel/echo-react";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});

您还可以指定模型事件负载数据的形状,提供更高的类型安全性和编辑便利性:

type User = {
    id: number;
    name: string;
    email: string;
};

useEchoModel<User, "App.Models.User">("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model.id);
    console.log(e.model.name);
});

客户端事件

使用 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);
    });

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