Skip to content
赞助商
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

事件广播

介绍

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

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

NOTE

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

配置

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

广播服务提供者

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

CSRF 令牌

Laravel Echo 需要访问当前会话的 CSRF 令牌。如果可用,Echo 将从 Laravel.csrfToken JavaScript 对象中提取令牌。此对象在您运行 make:auth Artisan 命令时创建的 resources/views/layouts/app.blade.php 布局中定义。如果您不使用此布局,可以在应用程序的 head HTML 元素中定义一个 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

接下来,您应该在 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.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 服务器配对,您需要在应用程序的 head HTML 元素中包含 Socket.IO JavaScript 客户端库。当 Socket.IO 服务器启动时,它将自动在标准 URL 上公开客户端 JavaScript 库。例如,如果您在与 Web 应用程序相同的域上运行 Socket.IO 服务器,您可以这样访问客户端库:

php
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>

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

php
import Echo from "laravel-echo"

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 的事件广播允许您使用基于驱动程序的方法通过 WebSockets 将服务器端 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
{
    //
}

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

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

授权频道

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

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

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

所有授权回调都会接收当前经过身份验证的用户作为第一个参数,任何其他通配符参数作为后续参数。在此示例中,我们使用 * 字符表示频道名称的“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';

授权频道

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

定义授权路由

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

php
Broadcast::routes();

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

php
Broadcast::routes($attributes);

定义授权回调

接下来,我们需要定义实际执行频道授权的逻辑。与定义授权路由一样,这也是在 BroadcastServiceProviderboot 方法中完成的。在此方法中,您可以使用 Broadcast::channel 方法注册频道授权回调:

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

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

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

广播事件

一旦您定义了一个事件并使用 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
this.$http.post('/task', task)
    .then((response) => {
        this.tasks.push(response.data);
    });

但是,请记住,我们还广播了任务的创建。如果您的 JavaScript 应用程序正在监听此事件以将任务添加到任务列表中,您将有重复的任务:一个来自端点,一个来自广播。

您可以通过使用 toOthers 方法来解决此问题,以指示广播器不向当前用户广播事件。

配置

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

如果您不使用 Vue 和 Vue Resource,您需要手动配置 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.*', 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) => {
        //
    });

通知

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

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

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

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