广播
介绍
在许多现代 Web 应用程序中,WebSockets 用于实现实时、实时更新的用户界面。当服务器上的某些数据更新时,通常会通过 WebSocket 连接发送消息以由客户端处理。这提供了一种更强大、高效的替代方案,以避免持续轮询应用程序以获取更改。
为了帮助您构建这些类型的应用程序,Laravel 使您可以轻松地通过 WebSocket 连接“广播”您的 事件。广播您的 Laravel 事件允许您在服务器端代码和客户端 JavaScript 应用程序之间共享相同的事件名称。
在深入了解事件广播之前,请确保您已阅读所有有关 Laravel 事件和监听器 的文档。
配置
您应用程序的所有事件广播配置都存储在 config/broadcasting.php
配置文件中。Laravel 开箱即支持几种广播驱动程序:Pusher Channels、Redis 和用于本地开发和调试的 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
标签:
<meta name="csrf-token" content="{{ csrf_token() }}">
驱动程序先决条件
Pusher Channels
如果您通过 Pusher Channels 广播事件,您应该使用 Composer 包管理器安装 Pusher Channels PHP SDK:
composer require pusher/pusher-php-server "~2.6"
接下来,您应该在 config/broadcasting.php
配置文件中配置您的 Channels 凭据。此文件中已包含一个 Channels 配置示例,允许您快速指定您的 Channels 密钥、密钥和应用程序 ID。config/broadcasting.php
文件的 pusher
配置还允许您指定 Channels 支持的其他 options
,例如集群:
'options' => [
'cluster' => 'eu',
'useTLS' => true
],
使用 Channels 和 Laravel Echo 时,您应该在 resources/assets/js/bootstrap.js
文件中实例化 Echo 实例时指定 pusher
作为所需的广播器:
import Echo from "laravel-echo"
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key'
});
Redis
如果您使用 Redis 广播器,您应该安装 Predis 库:
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 服务器,您可以这样访问客户端库:
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
接下来,您需要使用 socket.io
连接器和 host
实例化 Echo。
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 的事件广播允许您使用基于驱动程序的方法将服务器端 Laravel 事件广播到客户端 JavaScript 应用程序。目前,Laravel 附带 Pusher Channels 和 Redis 驱动程序。可以使用 Laravel Echo JavaScript 包轻松地在客户端上消费事件。
事件通过“频道”广播,这些频道可以指定为公共或私有。应用程序的任何访问者都可以在没有任何身份验证或授权的情况下订阅公共频道;但是,为了订阅私有频道,用户必须经过身份验证并被授权收听该频道。
使用示例应用程序
在深入了解事件广播的每个组件之前,让我们使用电子商务商店作为示例进行高层次概述。我们不会讨论配置 Pusher Channels 或 Laravel Echo 的细节,因为这些将在本文档的其他部分详细讨论。
在我们的应用程序中,假设我们有一个页面允许用户查看其订单的运输状态。假设当应用程序处理运输状态更新时,会触发一个 ShippingStatusUpdated
事件:
event(new ShippingStatusUpdated($update));
ShouldBroadcast
接口
当用户查看其订单之一时,我们不希望他们必须刷新页面以查看状态更新。相反,我们希望在创建更新时将其广播到应用程序。因此,我们需要使用 ShouldBroadcast
接口标记 ShippingStatusUpdated
事件。这将指示 Laravel 在事件触发时广播事件:
<?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
方法。此方法负责返回事件应在其上广播的频道。生成的事件类上已经定义了此方法的空存根,因此我们只需填写其详细信息。我们只希望订单的创建者能够查看状态更新,因此我们将在与订单相关的私有频道上广播事件:
/**
* 获取事件应在其上广播的频道。
*
* @return array
*/
public function broadcastOn()
{
return new PrivateChannel('order.'.$this->update->order_id);
}
授权频道
请记住,用户必须被授权才能收听私有频道。我们可以在 routes/channels.php
文件中定义我们的频道授权规则。在此示例中,我们需要验证任何尝试收听私有 order.1
频道的用户是否确实是订单的创建者:
Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
channel
方法接受两个参数:频道名称和一个回调,该回调返回 true
或 false
,指示用户是否被授权收听频道。
所有授权回调都会接收当前经过身份验证的用户作为第一个参数,并将任何其他通配符参数作为后续参数。在此示例中,我们使用 {orderId}
占位符来指示频道名称的“ID”部分是一个通配符。
监听事件广播
接下来,剩下的就是在我们的 JavaScript 应用程序中监听事件。我们可以使用 Laravel Echo 来做到这一点。首先,我们将使用 private
方法订阅私有频道。然后,我们可以使用 listen
方法监听 ShippingStatusUpdated
事件。默认情况下,事件的所有公共属性都将包含在广播事件中:
Echo.private(`order.${orderId}`)
.listen('ShippingStatusUpdated', (e) => {
console.log(e.update);
});
定义广播事件
要通知 Laravel 给定事件应被广播,请在事件类上实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast
接口。此接口已导入到框架生成的所有事件类中,因此您可以轻松地将其添加到任何事件中。
ShouldBroadcast
接口要求您实现一个方法:broadcastOn
。broadcastOn
方法应返回事件应在其上广播的频道或频道数组。频道应为 Channel
、PrivateChannel
或 PresenceChannel
的实例。Channel
的实例表示任何用户都可以订阅的公共频道,而 PrivateChannels
和 PresenceChannels
表示需要 频道授权 的私有频道:
<?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
方法来自定义广播名称:
/**
* 事件的广播名称。
*
* @return string
*/
public function broadcastAs()
{
return 'server.created';
}
如果您使用 broadcastAs
方法自定义广播名称,您应该确保使用前导 .
字符注册您的监听器。这将指示 Echo 不要将应用程序的命名空间添加到事件中:
.listen('.server.created', function (e) {
....
});
广播数据
当事件被广播时,其所有 public
属性将自动序列化并作为事件的负载广播,允许您从 JavaScript 应用程序访问其任何公共数据。因此,例如,如果您的事件有一个包含 Eloquent 模型的公共 $user
属性,事件的广播负载将是:
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}
但是,如果您希望对广播负载进行更细粒度的控制,可以向事件添加 broadcastWith
方法。此方法应返回您希望作为事件负载广播的数据数组:
/**
* 获取要广播的数据。
*
* @return array
*/
public function broadcastWith()
{
return ['id' => $this->user->id];
}
广播队列
默认情况下,每个广播事件都放置在 queue.php
配置文件中指定的默认队列连接的默认队列上。您可以通过在事件类上定义 broadcastQueue
属性来自定义广播器使用的队列。此属性应指定您希望在广播时使用的队列的名称:
/**
* 要将事件放置在其上的队列的名称。
*
* @var string
*/
public $broadcastQueue = 'your-queue-name';
如果您希望使用 sync
队列而不是默认队列驱动程序广播事件,可以实现 ShouldBroadcastNow
接口而不是 ShouldBroadcast
:
<?php
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class ShippingStatusUpdated implements ShouldBroadcastNow
{
//
}
广播条件
有时您希望仅在给定条件为真时广播事件。您可以通过向事件类添加 broadcastWhen
方法来定义这些条件:
/**
* 确定此事件是否应广播。
*
* @return bool
*/
public function broadcastWhen()
{
return $this->value > 100;
}
授权频道
私有频道要求您授权当前经过身份验证的用户是否可以实际收听频道。这是通过向您的 Laravel 应用程序发送带有频道名称的 HTTP 请求来完成的,并允许您的应用程序确定用户是否可以收听该频道。使用 Laravel Echo 时,授权订阅私有频道的 HTTP 请求将自动发出;但是,您需要定义适当的路由来响应这些请求。
定义授权路由
幸运的是,Laravel 使定义响应频道授权请求的路由变得容易。在包含在您的 Laravel 应用程序中的 BroadcastServiceProvider
中,您将看到对 Broadcast::routes
方法的调用。此方法将注册 /broadcasting/auth
路由以处理授权请求:
Broadcast::routes();
Broadcast::routes
方法将自动将其路由放置在 web
中间件组中;但是,如果您希望自定义分配的属性,可以将路由属性数组传递给该方法:
Broadcast::routes($attributes);
定义授权回调
接下来,我们需要定义实际执行频道授权的逻辑。这是在包含在您的应用程序中的 routes/channels.php
文件中完成的。在此文件中,您可以使用 Broadcast::channel
方法注册频道授权回调:
Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
channel
方法接受两个参数:频道名称和一个回调,该回调返回 true
或 false
,指示用户是否被授权收听频道。
所有授权回调都会接收当前经过身份验证的用户作为第一个参数,并将任何其他通配符参数作为后续参数。在此示例中,我们使用 {orderId}
占位符来指示频道名称的“ID”部分是一个通配符。
授权回调模型绑定
就像 HTTP 路由一样,频道路由也可以利用隐式和显式 路由模型绑定。例如,您可以请求实际的 Order
模型实例,而不是接收字符串或数字订单 ID:
use App\Order;
Broadcast::channel('order.{order}', function ($user, Order $order) {
return $user->id === $order->user_id;
});
广播事件
一旦您定义了一个事件并使用 ShouldBroadcast
接口标记它,您只需使用 event
函数触发事件。事件调度器将注意到事件标记为 ShouldBroadcast
接口,并将事件排队以进行广播:
event(new ShippingStatusUpdated($update));
仅对其他人
在构建利用事件广播的应用程序时,您可以用 broadcast
函数替换 event
函数。与 event
函数一样,broadcast
函数将事件调度到您的服务器端监听器:
broadcast(new ShippingStatusUpdated($update));
但是,broadcast
函数还公开了 toOthers
方法,允许您将当前用户排除在广播的接收者之外:
broadcast(new ShippingStatusUpdated($update))->toOthers();
为了更好地理解何时可能需要使用 toOthers
方法,让我们想象一个任务列表应用程序,其中用户可以通过输入任务名称来创建新任务。要创建任务,您的应用程序可能会向 /task
端点发出请求,该端点广播任务的创建并返回新任务的 JSON 表示。当您的 JavaScript 应用程序从端点接收到响应时,它可能会直接将新任务插入到其任务列表中,如下所示:
axios.post('/task', task)
.then((response) => {
this.tasks.push(response.data);
});
但是,请记住,我们还广播了任务的创建。如果您的 JavaScript 应用程序正在监听此事件以将任务添加到任务列表中,您将有重复的任务:一个来自端点,一个来自广播。
您可以通过使用 toOthers
方法来解决此问题,以指示广播器不向当前用户广播事件。
配置
当您初始化 Laravel Echo 实例时,会为连接分配一个 socket ID。如果您使用 Vue 和 Axios,socket ID 将自动附加到每个传出的请求中,作为 X-Socket-ID
标头。然后,当您调用 toOthers
方法时,Laravel 将从标头中提取 socket ID,并指示广播器不向任何具有该 socket ID 的连接广播。
如果您不使用 Vue 和 Axios,您需要手动配置 JavaScript 应用程序以发送 X-Socket-ID
标头。您可以使用 Echo.socketId
方法检索 socket ID:
var socketId = Echo.socketId();
接收广播
安装 Laravel Echo
Laravel Echo 是一个 JavaScript 库,使订阅频道和监听由 Laravel 广播的事件变得轻松。您可以通过 NPM 包管理器安装 Echo。在此示例中,我们还将安装 pusher-js
包,因为我们将使用 Pusher Channels 广播器:
npm install --save laravel-echo pusher-js
一旦安装了 Echo,您就可以在应用程序的 JavaScript 中创建一个新的 Echo 实例。一个很好的地方是在 Laravel 框架附带的 resources/assets/js/bootstrap.js
文件的底部:
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key'
});
创建使用 pusher
连接器的 Echo 实例时,您还可以指定 cluster
以及连接是否必须通过 TLS 进行(默认情况下,当 forceTLS
为 false
时,如果页面通过 HTTP 加载,或者作为 TLS 连接失败的回退,将进行非 TLS 连接):
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key',
cluster: 'eu',
forceTLS: true
});
监听事件
一旦您安装并实例化了 Echo,您就可以开始监听事件广播。首先,使用 channel
方法检索频道的实例,然后调用 listen
方法以监听指定的事件:
Echo.channel('orders')
.listen('OrderShipped', (e) => {
console.log(e.order.name);
});
如果您希望在私有频道上监听事件,请改用 private
方法。您可以继续链接调用 listen
方法以在单个频道上监听多个事件:
Echo.private('orders')
.listen(...)
.listen(...)
.listen(...);
离开频道
要离开频道,您可以在 Echo 实例上调用 leave
方法:
Echo.leave('orders');
命名空间
您可能已经注意到,在上面的示例中,我们没有为事件类指定完整的命名空间。这是因为 Echo 将自动假定事件位于 App\Events
命名空间中。但是,您可以在实例化 Echo 时通过传递 namespace
配置选项来配置根命名空间:
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key',
namespace: 'App.Other.Namespace'
});
或者,您可以在使用 Echo 订阅事件时为事件类添加 .
前缀。这将允许您始终指定完全限定的类名:
Echo.channel('orders')
.listen('.Namespace.Event.Class', (e) => {
//
});
存在频道
存在频道建立在私有频道的安全性之上,同时提供了意识到谁订阅了频道的附加功能。这使得构建强大、协作的应用程序功能变得容易,例如在另一个用户查看同一页面时通知用户。
授权存在频道
所有存在频道也是私有频道;因此,用户必须被 授权访问它们。但是,在为存在频道定义授权回调时,如果用户被授权加入频道,您将不会返回 true
。相反,您应该返回有关用户的数据数组。
授权回调返回的数据将在您的 JavaScript 应用程序中的存在频道事件监听器中可用。如果用户未被授权加入存在频道,您应该返回 false
或 null
:
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
if ($user->canJoinRoom($roomId)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
加入存在频道
要加入存在频道,您可以使用 Echo 的 join
方法。join
方法将返回一个 PresenceChannel
实现,该实现除了公开 listen
方法外,还允许您订阅 here
、joining
和 leaving
事件。
Echo.join(`chat.${roomId}`)
.here((users) => {
//
})
.joining((user) => {
console.log(user.name);
})
.leaving((user) => {
console.log(user.name);
});
here
回调将在频道成功加入后立即执行,并将接收一个包含所有当前订阅频道的其他用户信息的数组。当新用户加入频道时,将执行 joining
方法,而当用户离开频道时,将执行 leaving
方法。
广播到存在频道
存在频道可以像公共或私有频道一样接收事件。以聊天室为例,我们可能希望将 NewMessage
事件广播到房间的存在频道。为此,我们将从事件的 broadcastOn
方法返回一个 PresenceChannel
实例:
/**
* 获取事件应在其上广播的频道。
*
* @return Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('room.'.$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 应用程序的情况下将事件广播到其他连接的客户端。这对于“正在输入”通知等事情特别有用,您希望通知应用程序的用户,另一个用户正在给定屏幕上输入消息。要广播客户端事件,您可以使用 Echo 的 whisper
方法:
Echo.channel('chat')
.whisper('typing', {
name: this.user.name
});
要监听客户端事件,您可以使用 listenForWhisper
方法:
Echo.channel('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
通知
通过将事件广播与 通知 配对,您的 JavaScript 应用程序可以在不需要刷新页面的情况下接收新通知。首先,请务必阅读有关使用 广播通知频道 的文档。
一旦您配置了使用广播频道的通知,您可以使用 Echo 的 notification
方法监听广播事件。请记住,频道名称应与接收通知的实体的类名匹配:
Echo.private(`App.User.${userId}`)
.notification((notification) => {
console.log(notification.type);
});
在此示例中,通过 broadcast
频道发送到 App\User
实例的所有通知都将由回调接收。Laravel 框架附带的默认 BroadcastServiceProvider
中包含了 App.User.{id}
频道的频道授权回调。