广播


介绍

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

例如,想象一下你的应用程序能够将用户的数据导出为 CSV 文件并通过电子邮件发送给他们。然而,创建这个 CSV 文件需要几分钟的时间,所以你选择在排队的作业中创建和发送 CSV。当 CSV 文件已经创建并发送给用户时,我们可以使用事件广播来分发一个 App\Events\UserDataExported 事件,这个事件会被我们应用程序的 JavaScript 接收。一旦接收到事件,我们就可以向用户显示一条消息,告诉他们他们的 CSV 已经通过电子邮件发送给他们了,而不需要刷新页面。

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

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

支持的驱动程序

默认情况下,Laravel 包括两种用于服务器端广播的驱动程序:Pusher ChannelsAbly。然而,由社区驱动的 laravel-websocketssoketi 等包提供了其他不需要商业广播提供商的广播驱动程序。

在深入研究事件广播之前,请确保你已经阅读了 Laravel 关于事件和监听者的文档。

服务器端安装

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

通过服务器端广播驱动程序实现事件广播,它会将你的 Laravel 事件广播到你的浏览器客户端中的 Laravel Echo(一个 JavaScript 库)中,以便 Laravel Echo 可以接收它们。不要担心-我们将一步一步地介绍安装过程的每个部分。

配置

你的应用程序的所有事件广播配置都存储在 config/broadcasting.php 配置文件中。Laravel 支持几种广播驱动程序:Pusher ChannelsRedis和日志驱动程序(用于本地开发和调试)。此外,还包括了一个 null 驱动程序,允许你在测试期间完全禁用广播。在 config/broadcasting.php 配置文件中,为每个驱动程序包含了一个配置示例。

广播服务提供者

在广播任何事件之前,你首先需要注册 App\Providers\BroadcastServiceProvider。在新的 Laravel 应用程序中,你只需要取消注释配置文件 config/app.phpproviders 数组中的这个提供者。这个BroadcastServiceProvider 包含了注册广播授权路由和回调所需的代码。

队列配置

你还需要配置和运行一个队列worker。所有的事件广播都通过排队的作业来完成,以确保事件广播不会严重影响应用程序的响应时间。

Pusher Channels

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

composer require pusher/pusher-php-server

接下来,应在 config/broadcasting.php 配置文件中配置 Pusher Channels 凭据。该文件中已经包括了一个示例的 Pusher Channels 配置,使你能够快速指定你的密钥、密码和应用程序ID。通常,这些值应通过PUSHER_APP_KEYPUSHER_APP_SECRETPUSHER_APP_ID 环境变量设置:

PUSHER_APP_ID=your-pusher-app-id 
PUSHER_APP_KEY=your-pusher-key 
PUSHER_APP_SECRET=your-pusher-secret 
PUSHER_APP_CLUSTER=mt1

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

接下来,你需要在 .env 文件中将广播驱动程序更改为 pusher

BROADCAST_DRIVER=pusher

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

开源的 Pusher 替代方案

laravel-websocketssoketi 软件包提供了与 Pusher 兼容的 Laravel WebSocket 服务器。这些软件包允许你在没有商业 WebSocket 提供商的情况下充分利用 Laravel 广播的功能。有关安装和使用这些软件包的更多信息,请参阅我们关于开源替代方案的文档。

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 文件中将广播驱动程序更改为 ably

BROADCAST_DRIVER=ably

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

开源替代方案

PHP

laravel-websockets 包是一个纯PHP的 Laravel Pusher 兼容 WebSocket 包。这个包使你能够在没有商业 WebSocket 提供商的情况下充分利用 Laravel 广播的功能。有关安装和使用此包的更多信息,请参阅官方文档

Node

Soketi 是一个基于 Node 的、Laravel Pusher 兼容的 WebSocket 服务器。在内部,soketi 使用 µWebSockets.js 实现极高的可扩展性和速度。这个包使你能够在没有商业 WebSocket 提供商的情况下充分利用 Laravel 广播的功能。有关安装和使用此包的更多信息,请参阅官方文档

客户端安装

Pusher Channels

Laravel Echo 是一个 JavaScript 库,它使得订阅频道并监听服务器端广播的事件变得简单。你可以通过 NPM 包管理器安装 Echo。在这个示例中,我们还将安装 pusher-js 包,因为我们将使用 Pusher Channels 广播器:

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

安装 Echo 后,你可以在应用程序的 JavaScript 代码中创建一个新的 Echo 实例。一个很好的位置是resources/js/bootstrap.js 文件的底部,这个文件是 Laravel 框架提供的。默认情况下,在这个文件中已经包含了一个示例的 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 
});

取消注释并根据你的需要调整Echo配置后,可以编译应用程序的资产:

npm run dev

有关编译应用程序的JavaScript资产的详细信息,请参阅 Vite 的文档。

使用现有的客户端实例

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

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
 
const options = {
    broadcaster: 'pusher',
    key: 'your-pusher-channels-key'
}
 
window.Echo = new Echo({
    ...options,
    client: new Pusher(options.key, options)
});

Ably

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

Laravel Echo 是一个 JavaScript 库,它使得订阅频道并监听服务器端广播的事件变得简单。你可以通过 NPM 包管理器安装 Echo。在这个示例中,我们也将安装 pusher-js 包。

你可能会想为什么我们要安装 pusher-js JavaScript 库,即使我们使用 Ably 来广播我们的事件。幸运的是,Ably 包含了一个 Pusher 兼容模式,可以让我们在客户端应用程序中使用 Pusher 协议来监听事件:

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

在继续之前,请打开你的 Ably 应用程序设置,启用 Pusher 协议支持。你可以在 Ably 应用程序设置仪表板的"Protocol Adapter Settings"部分中启用这个功能。

安装 Echo 后,你可以在应用程序的 JavaScript 代码中创建一个新的 Echo 实例。一个很好的位置是resources/js/bootstrap.js 文件的底部,这个文件是 Laravel 框架提供的。默认情况下,这个文件中已经包含了一个示例的 Echo 配置;然而,bootstrap.js 文件中的默认配置是为 Pusher 准备的。你可以复制下面的配置来将配置转换为 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 的事件广播允许你使用基于驱动程序的方法将服务器端 Laravel 事件广播到客户端 JavaScript 应用程序中的 WebSockets。目前,Laravel 配备了 Pusher ChannelsAbly 驱动程序。通过 Laravel Echo JavaScript 包,可以在客户端轻松消费这些事件。

事件通过"通道"进行广播,可以指定为公共或私有。任何访问你的应用程序的用户都可以订阅一个公共通道,而无需任何身份验证或授权;但是,为了订阅一个私有通道,用户必须经过身份验证并被授权在该通道上监听。

如果你想探索 Pusher 的开源替代方案,请查看开源替代方案

使用示例应用程序

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

在我们的应用程序中,假设我们有一个页面,允许用户查看他们订单的发货状态。假设当应用程序处理发货状态更新时,发送一个 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\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 来实现这一点。首先,我们使用 private 方法订阅私有通道。然后,我们可以使用 listen 方法来监听OrderShipmentStatusUpdated 事件。默认情况下,广播事件上的所有公共属性都将包含在广播事件中:

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

定义广播事件

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

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

<?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 应用程序中访问其公共数据。因此,例如,如果您的事件有一个单一的公共 $user 属性,其中包含一个 Eloquent 模型,那么事件的广播有效载荷将是:

{
    "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';
}

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

<?php
 
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,您仍然可以通过在事件类上定义一个 $afterCommit 属性来指示特定的广播事件在所有打开的数据库事务提交之后立即派发:

<?php
 
namespace App\Events;
 
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
 
class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;
 
    public $afterCommit = true;
}

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

授权通道

私有通道要求您授权当前认证的用户实际可以监听该通道。这可以通过向 Laravel 应用程序发送一个带有通道名称的 HTTP 请求,并允许应用程序确定用户是否可以监听该通道来实现。使用 Laravel Echo 时,订阅私有通道的 HTTP 请求将自动发送;但是,您需要定义正确的路由来响应这些请求。

定义授权路由

幸运的是,Laravel 使您可以轻松定义路由来响应通道授权请求。在您的 Laravel 应用程序中的 App\Providers\BroadcastServiceProvider 中,您会看到调用 Broadcast::routes 方法。这个方法将注册 /broadcasting/auth 路由来处理授权请求:

Broadcast::routes();

Broadcast::routes 方法将自动将其路由放在 web 中间件组中;但是,如果您想自定义分配的属性,可以向方法传递一个路由属性数组:

Broadcast::routes($attributes);

自定义授权端点

默认情况下,Echo 将使用 /broadcasting/auth 端点来授权通道访问。不过,您可以通过将 authEndpoint 配置选项传递给 Echo 实例来指定自己的授权端点:

window.Echo = new Echo({
    broadcaster: 'pusher',
    // ...
    authEndpoint: '/custom/endpoint/auth'
});

自定义授权请求

您可以通过在初始化 Echo 时提供自定义 authorizer 来自定义 Laravel Echo 的授权请求方式:

window.Echo = new Echo({
    // ...
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                axios.post('/api/broadcasting/auth', {
                    socket_id: socketId,
                    channel_name: channel.name
                })
                .then(response => {
                    callback(null, response.data);
                })
                .catch(error => {
                    callback(error);
                });
            }
        };
    },
})

定义授权回调

接下来,我们需要定义实际确定当前认证用户是否可以在给定通道上监听的逻辑。这是在 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 路由一样,通道路由也可以利用隐式和显式的路由模型绑定。例如,您可以请求一个实际的订单模型实例,而不是接收一个字符串或数值的订单 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 发出请求,该请求广播任务的创建并返回新任务的 JSON 表示。当您的 JavaScript 应用程序从端点接收到响应时,它可能会直接将新任务插入到任务列表中,如下所示:

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

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

您的事件必须使用 Illuminate\Broadcasting\InteractsWithSockets trait 才能调用 toOthers 方法。

配置

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

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

var socketId = Echo.socketId();

自定义连接

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

use App\Events\OrderShipmentStatusUpdated;
 
broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');

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

<?php
 
namespace App\Events;
 
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
 
class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    use InteractsWithBroadcasting;
 
    /**
     * Create a new event instance.
     */
    public function __construct()
    {
        $this->broadcastVia('pusher');
    }
}

接收广播

监听事件

安装并实例化 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 时配置根命名空间,通过传递一个命名空间配置选项:

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 方法会在用户离开通道时执行。error 方法会在认证端点返回的 HTTP 状态码不是 200 或解析返回的 JSON 时执行。

广播给存在通道

存在通道可以像公共或私有通道一样接收事件。以聊天室为例,我们可能希望将 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 将自动使用模型的类名和主键标识符作为通道名称来实例化模型的私有通道实例。

因此,一个 ID 为 1App\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 方法。例如,对于 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 的事件。

如果需要,您可以通过添加 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('.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);
    });

在此示例中,通过广播通道发送到 App\Models\User 实例的所有通知都将由回调接收。Laravel 框架随附的默认 BroadcastServiceProvider 中包含了一个用于 App.Models.User.{id} 通道的授权通道回调。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: Artisan 控制台

>> 下一篇: 缓存