Skip to content

广播

介绍

在许多现代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

lightbulb

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

shell
php artisan install:broadcasting

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

队列配置

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

Reverb

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

sh
composer require laravel/reverb

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

sh
php artisan reverb:install

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

Pusher Channels

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

shell
composer require pusher/pusher-php-server

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

ini
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

ini
BROADCAST_CONNECTION=pusher

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

Ably

lightbulb

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

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

shell
composer require ably/ably-php

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

ini
ABLY_KEY=your-ably-key

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

ini
BROADCAST_CONNECTION=ably

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

客户端安装

Reverb

Laravel Echo是一个JavaScript库,使得订阅频道和监听服务器端广播的事件变得轻而易举。您可以通过NPM包管理器安装Echo。在此示例中,我们还将安装pusher-js包,因为Reverb利用Pusher协议进行WebSocket订阅、频道和消息:

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

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

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,
    wssPort: import.meta.env.VITE_REVERB_PORT,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

接下来,您应编译应用程序的资产:

shell
npm run build
exclamation

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

Pusher Channels

Laravel Echo是一个JavaScript库,使得订阅频道和监听服务器端广播的事件变得轻而易举。Echo还利用pusher-js NPM包实现Pusher协议进行WebSocket订阅、频道和消息。

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

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

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

js
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文件中尚不存在,您应添加它们:

ini
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配置,您可以编译应用程序的资产:

shell
npm run build
lightbulb

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

使用现有客户端实例

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

js
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

lightbulb

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

Laravel Echo是一个JavaScript库,使得订阅频道和监听服务器端广播的事件变得轻而易举。Echo还利用pusher-js NPM包实现Pusher协议进行WebSocket订阅、频道和消息。

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

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

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

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

js
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配置,您可以编译应用程序的资产:

shell
npm run dev
lightbulb

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

概念概述

Laravel的事件广播允许您将服务器端Laravel事件广播到客户端JavaScript应用程序,使用基于驱动的WebSocket方法。目前,Laravel提供了Pusher ChannelsAbly驱动。事件可以通过Laravel Echo JavaScript包轻松在客户端消费。

事件通过“频道”广播,可以指定为公共或私有。任何访问您应用程序的访客都可以在没有任何身份验证或授权的情况下订阅公共频道;然而,为了订阅私有频道,用户必须经过身份验证并获得授权以收听该频道。

使用示例应用程序

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

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

php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);

ShouldBroadcast接口

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

php
<?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方法。该方法负责返回事件应广播的频道。生成的事件类上已经定义了该方法的空存根,因此我们只需填写其详细信息。我们只希望订单的创建者能够查看状态更新,因此我们将在与订单相关的私有频道上广播事件:

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

/**
 * 获取事件应广播的频道。
 */
public function broadcastOn(): Channel
{
    return new PrivateChannel('orders.'.$this->order->id);
}

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

php
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频道的用户是否确实是订单的创建者:

php
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事件。默认情况下,事件的所有公共属性都将包含在广播事件中:

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

定义广播事件

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

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

php
<?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方法来自定义广播名称:

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

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

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

广播数据

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

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

但是,如果您希望对广播有效负载有更细粒度的控制,您可以在事件中添加一个broadcastWith方法。该方法应返回您希望作为事件有效负载广播的数据数组:

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

广播队列

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

php
/**
 * 广播事件时使用的队列连接的名称。
 *
 * @var string
 */
public $connection = 'redis';

/**
 * 放置广播作业的队列名称。
 *
 * @var string
 */
public $queue = 'default';

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

php
/**
 * 放置广播作业的队列名称。
 */
public function broadcastQueue(): string
{
    return 'default';
}

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

php
<?php

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
    // ...
}

广播条件

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

php
/**
 * 确定此事件是否应广播。
 */
public function broadcastWhen(): bool
{
    return $this->order->value > 100;
}

广播与数据库事务

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

如果您的队列连接的after_commit配置选项设置为false,您仍然可以通过在事件类上实现ShouldDispatchAfterCommit接口来指示特定广播事件应在所有打开的数据库事务提交后调度:

php
<?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;
}
lightbulb

要了解有关解决这些问题的更多信息,请查阅关于排队作业和数据库事务的文档。

授权频道

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

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

定义授权回调

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

php
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命令查看应用程序的广播授权回调列表:

shell
php artisan channel:list

授权回调模型绑定

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

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

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

与HTTP路由模型绑定不同,频道模型绑定不支持自动隐式模型绑定范围。然而,这通常不是问题,因为大多数频道可以基于单个模型的唯一主键进行范围限制。

授权回调身份验证

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

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

定义频道类

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

shell
php artisan make:channel OrderChannel

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

php
use App\Broadcasting\OrderChannel;

Broadcast::channel('orders.{order}', OrderChannel::class);

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

php
<?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;
    }
}
lightbulb

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

广播事件

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

php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);

仅对其他人

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

php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->toOthers();

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

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

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

exclamation

您的事件必须使用Illuminate\Broadcasting\InteractsWithSockets特性,以便调用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:

js
var socketId = Echo.socketId();

自定义连接

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

php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');

或者,您可以通过在事件的构造函数中调用broadcastVia方法来指定事件的广播连接。然而,在这样做之前,您应确保事件类使用InteractsWithBroadcasting特性:

php
<?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门面允许您广播“匿名事件”:

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

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

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

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

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

上面的示例将广播一个事件,如下所示:

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

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

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

使用send方法广播匿名事件会将事件调度到您的应用程序的队列进行处理。然而,如果您希望立即广播事件,您可以使用sendNow方法:

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

要将事件广播给所有频道订阅者,但不包括当前经过身份验证的用户,您可以调用toOthers方法:

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

接收广播

监听事件

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

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

如果您希望在私有频道上监听事件,请使用private方法。您可以继续链式调用listen方法,以在单个频道上监听多个事件:

js
Echo.private(`orders.${this.order.id}`)
    .listen(/* ... */)
    .listen(/* ... */)
    .listen(/* ... */);

停止监听事件

如果您希望在不离开频道的情况下停止监听给定事件,您可以使用stopListening方法:

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

离开频道

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

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

如果您希望离开频道及其相关的私有和存在频道,您可以调用leave方法:

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

命名空间

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

js
window.Echo = new Echo({
    broadcaster: 'pusher',
    // ...
    namespace: 'App.Other.Namespace'
});

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

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

存在频道

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

授权存在频道

所有存在频道也是私有频道;因此,用户必须获得授权访问它们。然而,在为存在频道定义授权回调时,您不会返回true,以指示用户获得加入频道的授权。相反,您应该返回有关用户的数据数组。

返回的授权回调数据将在JavaScript应用程序的存在频道事件监听器中可用。如果用户未获得加入存在频道的授权,则应返回falsenull

php
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事件。

js
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方法将在用户离开频道时执行。error方法将在身份验证端点返回非200的HTTP状态代码时执行,或者在解析返回的JSON时出现问题时执行。

广播到存在频道

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

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

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

php
broadcast(new NewMessage($message));

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

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

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

模型广播

exclamation

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

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

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

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

php
<?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的值。通过检查该变量的值,您可以确定模型在特定事件上应广播到哪些频道:

php
/**
 * 获取模型事件应广播的频道。
 *
 * @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实例:

php
use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;

/**
 * 为模型创建一个新的可广播模型事件。
 */
protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
    return (new BroadcastableModelEventOccurred(
        $this, $event
    ))->dontBroadcastToCurrentUser();
}

模型广播约定

频道约定

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

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

php
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将使用上述讨论的模型频道约定将模型转换为频道名称字符串:

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

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

php
$user->broadcastChannel()

事件约定

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

因此,例如,更新App\Models\Post模型将广播事件到您的客户端应用程序,事件名称为PostUpdated,有效负载如下:

json
{
    "model": {
        "id": 1,
        "title": "My first post"
        ...
    },
    ...
    "socket": "someSocketId",
}

删除App\Models\User模型将广播事件,事件名称为UserDeleted

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

php
/**
 * 模型事件的广播名称。
 */
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属性,其中包含模型的所有可广播属性:

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

客户端事件

lightbulb

使用Pusher Channels时,您必须在应用程序仪表板的“应用设置”部分启用“客户端事件”选项,以便发送客户端事件。

有时,您可能希望向其他连接的客户端广播事件,而无需访问Laravel应用程序。这对于“正在输入”通知等功能特别有用,您希望提醒应用程序的用户,另一个用户正在给定屏幕上输入消息。

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

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

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

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

通知

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

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

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

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