Skip to content

广播

介绍

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

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

lightbulb

在深入了解事件广播之前,请确保您已阅读所有有关 Laravel 事件和监听器 的文档。

配置

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

广播服务提供者

在广播任何事件之前,您首先需要注册 App\Providers\BroadcastServiceProvider。在新的 Laravel 应用程序中,您只需在 config/app.php 配置文件的 providers 数组中取消注释此提供者即可。此提供者将允许您注册广播授权路由和回调。

CSRF 令牌

Laravel Echo 需要访问当前会话的 CSRF 令牌。您应该验证应用程序的 head HTML 元素定义了包含 CSRF 令牌的 meta 标签:

php
<meta name="csrf-token" content="{{ csrf_token() }}">

驱动程序先决条件

Pusher Channels

如果您通过 Pusher Channels 广播事件,您应该使用 Composer 包管理器安装 Pusher Channels PHP SDK:

php
composer require pusher/pusher-php-server "~3.0"

接下来,您应该在 config/broadcasting.php 配置文件中配置您的 Channels 凭据。此文件中已包含一个 Channels 配置示例,允许您快速指定您的 Channels 密钥、密钥和应用程序 ID。config/broadcasting.php 文件的 pusher 配置还允许您指定 Channels 支持的其他 options,例如集群:

php
'options' => [
    'cluster' => 'eu',
    'useTLS' => true
],

使用 Channels 和 Laravel Echo 时,您应该在 resources/assets/js/bootstrap.js 文件中实例化 Echo 实例时指定 pusher 作为所需的广播器:

php
import Echo from "laravel-echo"

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-channels-key'
});

Redis

如果您使用 Redis 广播器,您应该安装 Predis 库:

php
composer require predis/predis

Redis 广播器将使用 Redis 的 pub / sub 功能广播消息;但是,您需要将其与可以接收来自 Redis 的消息并将其广播到您的 WebSocket 频道的 WebSocket 服务器配对。

当 Redis 广播器发布事件时,它将发布在事件指定的频道名称上,负载将是一个 JSON 编码的字符串,包含事件名称、data 负载和生成事件的用户的 socket ID(如果适用)。

Socket.IO

如果您打算将 Redis 广播器与 Socket.IO 服务器配对,您需要在应用程序中包含 Socket.IO JavaScript 客户端库。您可以通过 NPM 包管理器安装它:

php
npm install --save socket.io-client

接下来,您需要使用 socket.io 连接器和 host 实例化 Echo。

php
import Echo from "laravel-echo"

window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

最后,您需要运行一个兼容的 Socket.IO 服务器。Laravel 不包含 Socket.IO 服务器实现;但是,社区驱动的 Socket.IO 服务器目前在 tlaverdure/laravel-echo-server GitHub 仓库中维护。

队列先决条件

在广播事件之前,您还需要配置和运行 队列监听器。所有事件广播都是通过队列作业完成的,以便不会严重影响应用程序的响应时间。

概念概述

Laravel 的事件广播允许您使用基于驱动程序的方法将服务器端 Laravel 事件广播到客户端 JavaScript 应用程序。目前,Laravel 附带 Pusher Channels 和 Redis 驱动程序。可以使用 Laravel Echo JavaScript 包轻松地在客户端上消费事件。

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

使用示例应用程序

在深入了解事件广播的每个组件之前,让我们使用电子商务商店作为示例进行高层次概述。我们不会讨论配置 Pusher ChannelsLaravel Echo 的细节,因为这些将在本文档的其他部分详细讨论。

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

php
event(new ShippingStatusUpdated($update));

ShouldBroadcast 接口

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

php
<?php

namespace App\Events;

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

class ShippingStatusUpdated implements ShouldBroadcast
{
    /**
     * 有关运输状态更新的信息。
     *
     * @var string
     */
    public $update;
}

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

php
/**
 * 获取事件应广播的频道。
 *
 * @return \Illuminate\Broadcasting\Channel|array
 */
public function broadcastOn()
{
    return new PrivateChannel('order.'.$this->update->order_id);
}

授权频道

请记住,用户必须被授权才能收听私有频道。我们可以在 routes/channels.php 文件中定义我们的频道授权规则。在此示例中,我们需要验证任何尝试收听私有 order.1 频道的用户是否确实是订单的创建者:

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

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

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

监听事件广播

接下来,剩下的就是在我们的 JavaScript 应用程序中监听事件。我们可以使用 Laravel Echo 来做到这一点。首先,我们将使用 private 方法订阅私有频道。然后,我们可以使用 listen 方法来监听 ShippingStatusUpdated 事件。默认情况下,事件的所有公共属性都将包含在广播事件中:

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

定义广播事件

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

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

php
<?php

namespace App\Events;

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

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    public $user;

    /**
     * 创建一个新的事件实例。
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

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

然后,您只需像往常一样 触发事件。一旦事件被触发,队列作业 将自动通过您指定的广播驱动程序广播事件。

广播名称

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

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

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

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

广播数据

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

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

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

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

广播队列

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

php
/**
 * 要放置事件的队列名称。
 *
 * @var string
 */
public $broadcastQueue = 'your-queue-name';

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

php
<?php

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class ShippingStatusUpdated implements ShouldBroadcastNow
{
    //
}

广播条件

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

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

授权频道

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

定义授权路由

幸运的是,Laravel 使定义响应频道授权请求的路由变得容易。在您的 Laravel 应用程序中包含的 BroadcastServiceProvider 中,您将看到对 Broadcast::routes 方法的调用。此方法将注册 /broadcasting/auth 路由以处理授权请求:

php
Broadcast::routes();

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

php
Broadcast::routes($attributes);

定义授权回调

接下来,我们需要定义实际执行频道授权的逻辑。这是在应用程序中包含的 routes/channels.php 文件中完成的。在此文件中,您可以使用 Broadcast::channel 方法注册频道授权回调:

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

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

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

授权回调模型绑定

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

php
use App\Order;

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

定义频道类

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

php
php artisan make:channel OrderChannel

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

php
use App\Broadcasting\OrderChannel;

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

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

php
<?php

namespace App\Broadcasting;

use App\User;
use App\Order;

class OrderChannel
{
    /**
     * 创建一个新的频道实例。
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * 验证用户对频道的访问权限。
     *
     * @param  \App\User  $user
     * @param  \App\Order  $order
     * @return array|bool
     */
    public function join(User $user, Order $order)
    {
        return $user->id === $order->user_id;
    }
}
lightbulb

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

广播事件

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

php
event(new ShippingStatusUpdated($update));

仅对其他人

在构建利用事件广播的应用程序时,您可以用 broadcast 函数替换 event 函数。与 event 函数一样,broadcast 函数将事件分派给您的服务器端监听器:

php
broadcast(new ShippingStatusUpdated($update));

但是,broadcast 函数还公开了 toOthers 方法,该方法允许您将当前用户排除在广播的接收者之外:

php
broadcast(new ShippingStatusUpdated($update))->toOthers();

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

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

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

exclamation

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

配置

当您初始化 Laravel Echo 实例时,会为连接分配一个 socket ID。如果您使用 VueAxios,socket ID 将自动附加到每个传出的请求作为 X-Socket-ID 头。然后,当您调用 toOthers 方法时,Laravel 将从头中提取 socket ID 并指示广播器不向具有该 socket ID 的任何连接广播。

如果您不使用 Vue 和 Axios,您需要手动配置您的 JavaScript 应用程序以发送 X-Socket-ID 头。您可以使用 Echo.socketId 方法检索 socket ID:

php
var socketId = Echo.socketId();

接收广播

安装 Laravel Echo

Laravel Echo 是一个 JavaScript 库,使订阅频道和监听由 Laravel 广播的事件变得轻松。您可以通过 NPM 包管理器安装 Echo。在此示例中,我们还将安装 pusher-js 包,因为我们将使用 Pusher Channels 广播器:

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

一旦安装了 Echo,您就可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。一个很好的地方是在 Laravel 框架中包含的 resources/assets/js/bootstrap.js 文件的底部:

php
import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-channels-key'
});

创建使用 pusher 连接器的 Echo 实例时,您还可以指定 cluster 以及连接是否必须通过 TLS 进行(默认情况下,当 forceTLSfalse 时,如果页面通过 HTTP 加载,或者如果 TLS 连接失败,则会建立非 TLS 连接):

php
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-channels-key',
    cluster: 'eu',
    forceTLS: true
});

监听事件

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

php
Echo.channel('orders')
    .listen('OrderShipped', (e) => {
        console.log(e.order.name);
    });

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

php
Echo.private('orders')
    .listen(...)
    .listen(...)
    .listen(...);

离开频道

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

php
Echo.leave('orders');

命名空间

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

php
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-channels-key',
    namespace: 'App.Other.Namespace'
});

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

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

存在频道

存在频道建立在私有频道的安全性之上,同时提供了意识到谁订阅了频道的附加功能。这使得构建强大、协作的应用程序功能变得容易,例如在另一个用户查看同一页面时通知用户。

授权存在频道

所有存在频道也是私有频道;因此,用户必须被 授权访问它们。但是,在为存在频道定义授权回调时,如果用户被授权加入频道,您将不会返回 true。相反,您应该返回有关用户的数据数组。

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

php
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});

加入存在频道

要加入存在频道,您可以使用 Echo 的 join 方法。join 方法将返回一个 PresenceChannel 实现,该实现除了公开 listen 方法外,还允许您订阅 herejoiningleaving 事件。

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

here 回调将在频道成功加入后立即执行,并将接收一个包含所有其他用户的用户信息的数组。joining 方法将在新用户加入频道时执行,而 leaving 方法将在用户离开频道时执行。

广播到存在频道

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

php
/**
 * 获取事件应广播的频道。
 *
 * @return Channel|array
 */
public function broadcastOn()
{
    return new PresenceChannel('room.'.$this->message->room_id);
}

像公共或私有事件一样,存在频道事件可以使用 broadcast 函数广播。与其他事件一样,您可以使用 toOthers 方法将当前用户排除在接收广播之外:

php
broadcast(new NewMessage($message));

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

您可以通过 Echo 的 listen 方法监听加入事件:

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

客户端事件

lightbulb

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

有时,您可能希望在不访问 Laravel 应用程序的情况下将事件广播到其他连接的客户端。这对于诸如“正在输入”通知之类的事情特别有用,您希望在给定屏幕上向应用程序的用户发出警报,告知另一个用户正在输入消息。

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

php
Echo.private('chat')
    .whisper('typing', {
        name: this.user.name
    });

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

php
Echo.private('chat')
    .listenForWhisper('typing', (e) => {
        console.log(e.name);
    });

通知

通过将事件广播与 通知 配对,您的 JavaScript 应用程序可以在不需要刷新页面的情况下接收新通知。首先,请务必阅读有关使用 广播通知频道 的文档。

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

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

在此示例中,通过 broadcast 频道发送到 App\User 实例的所有通知都将由回调接收。默认的 BroadcastServiceProvider 中包含了 App.User.{id} 频道的频道授权回调,该提供者随 Laravel 框架一起提供。