Laravel 广播
简介
在许多现代的Web应用程序中,WebSocket用于实现实时更新的用户界面。在服务器上更新某些数据时,通常会通过WebSocket连接发送一条消息给客户端进行处理。
为了帮助构建这些类型的应用程序,Laravel可以轻松的在WebSocket链接上“广播”我们的事件。在Laravel中通过广播相应的事件可在服务器端代码和客户端JavaScript应用程序之间共享相同的事件名称。
配置
应用程序的所有事件广播的配置都存储在配置文件config/broadcasting.php
中。Laravel自带支持多种广播驱动程序:Pusher Channels
,Redis
和log
。其中log
驱动程序用于本地开发和调试。此外,还包括一个null
驱动程序,该驱动程序可让完全禁用广播。在config/broadcasting.php
配置文件中为每个驱动程序提供了一个配置示例。
广播 Service Provider
在广播任何事件之前,我们首先需要注册App\Providers\BroadcastServiceProvider
。在新的Laravel应用程序中,只需要在配置文件config/app.php中的providers数组中取消注释。该provider 将允许我们注册广播授权路由和回调。
CSRF Token
Laravel Echo
需要访问当前会话的CSRF Token 。应该验证应用程序的HTML head标签中是否定义了包含CSRF token的meta元素:
<meta name="csrf-token" content="{{ csrf_token() }}">
驱动程序先决条件
Pusher Channel
如果要通过Pusher Channels广播事件,则应使用Composer软件包管理器安装Pusher Channels
:
$ composer require pusher/pusher-php-server "~4.0"
接下来,应该在config/broadcasting.php
配置文件中配置channel 凭据。该文件中已经包含一个Channels配置的示例,使我们可以快速指定Channels key,encrypt和应用程序ID。我们还可以在config/broadcasting.php
文件的pusher配置中指定options通道支持的其他功能,例如群集:
'options' => [
'cluster' => 'eu',
'useTLS' => true
],
当使用Channels和Laravel Echo时,我们应该在resources/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'
});
最后,你需要将你的广播驱动程序在.env
文件中更改为pusher:
BROADCAST_DRIVER=pusher
Pusher兼容Laravel Websocket
laravel-WebSockets是一个纯PHP,一个实现Pusher的WebSocket应用包。该软件包使我们无需添加外部Websocket Provider 或Node即可充分利用Laravel广播的全部功能。
Redis
如果使用Redis广播器,则应该通过PECL安装phpredis PHP扩展,或者通过Composer安装Predis库:
$ composer require predis/predis
接下来,应该将.env中广播驱动程序的配置改为redis:
BROADCAST_DRIVER=redis
Redis广播将使用Redis的发布/订阅功能广播消息;但是,我们需要将此服务器与WebSocket服务器配对,该服务器可以接收来自Redis的消息并将其广播到WebSocket通道。
当Redis广播发布事件时,它将在事件的指定频道名称上发布,并且有效负载将是JSON编码的字符串,其中包含事件名称,data有效负载以及生成事件套接字ID的用户(如果适用)。
Socket.IO
如果要将Redis广播程序与Socket.IO服务器配对,则需要在应用程序中包含Socket.IO JavaScript客户端库。可以通过NPM软件包管理器进行安装:
$ npm install --save socket.io-client
接下来,需要使用socket.io连接器和host 来实例化Echo。
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服务器实现;但是,当前在tlaverdure/laravel-echo-server
GitHub存储库中有相关的代码。
队列先决条件
在广播事件之前,还需要配置并运行队列监听器。所有事件广播都是通过排队的作业完成的,因此不会严重影响应用程序的响应时间。
概念概述
Laravel的事件广播允许您使用基于驱动程序的WebSockets方法将服务器端Laravel事件广播到客户端的JavaScript应用程序。目前,Laravel附带Pusher Channels
和Redis
驱动程序。使用Laravel Echo
Javascript包可以很容易地在客户端使用这些事件。
事件通过“频道”广播,可以指定为公共频道或私有频道。应用程序的任何访问者都可以订阅公共频道,而无需任何身份验证或授权;但是,要想订阅私有频道,必须对用户进行身份验证并授权其在该频道上收听。
应用程序示例
在深入探讨事件广播的每个组成部分之前,让我们以电子商务商店为例进行高级概述。
在我们的应用程序中,假设我们有一个页面,允许用户查看其订单的发货状态。我们还假设当应用程序处理运输状态更新时会触发一个ShippingStatusUpdated
事件:
event(new ShippingStatusUpdated($update));
ShouldBroadcast接口
当用户查看其订单时,我们不希望他们必须刷新页面以查看状态更新。相反,我们希望在创建更新时向应用程序广播更新。因此,我们需要使用ShouldBroadcast接口标记ShippingStatusUpdated事件。这将指示Laravel在事件触发时广播该事件:
<?php
namespace App\Events;
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 ShippingStatusUpdated implements ShouldBroadcast
{
/**
* Information about the shipping status update.
*
* @var string
*/
public $update;
}
ShouldBroadcast接口要求我们的事件定义一个broadcastOn
方法。此方法负责返回事件应在其上广播的频道。在生成的事件类上已经定义了此方法的空存根,因此我们只需要填写其详细信息即可。我们只希望订单的创建者能够查看状态更新,因此我们将在与订单相关的专用频道上广播事件:
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\PrivateChannel
*/
public function broadcastOn()
{
return new PrivateChannel('order.'.$this->update->order_id);
}
授权 channels
请记住,必须授权用户在私有频道上收听。我们可以在routes/channels.php
文件中定义我们的频道授权规则。在此示例中,我们需要验证任何尝试在专用order.1频道上收听的用户是不是订单的创建者:
Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
channel方法接受两个参数:通道的名称和一个回调函数,如果用户被授权在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
。该方法应返回事件应在其上广播的一个通道或一组通道。这些channels应该是Channel
,PrivateChannel
或PresenceChannel
的实例。Channel
的实例表示公共频道,任何用户可以订阅,而PrivateChannels
和PresenceChannels
实例表示专用频道需要授权:
<?php
namespace App\Events;
use App\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 $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the channels the event should broadcast on.
*
* @return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('user.'.$this->user->id);
}
}
然后,只需要照常触发事件即可。触发事件后,排队的作业将通指定的广播驱动程序自动广播事件。
广播名称
默认情况下,Laravel将使用事件的类名广播事件。但是,您可以通过定义事件的broadcastAs
方法来自定义广播名称:
/**
* The event's broadcast name.
*
* @return string
*/
public function broadcastAs()
{
return 'server.created';
}
如果使用broadcastAs方法自定义广播名称,则应确保注册的listener前面带有.
。这将指示Echo不要在应用程序的命名空间之前添加事件:
.listen('.server.created', function (e) {
....
});
广播数据
广播事件时,其所有public属性都将自动序列化并作为事件的有效负载广播,从而使我们可以从JavaScript应用程序中访问其任何公共数据。因此,例如,如果事件具有一个公共属性$user
,该属性是一个Eloquent模型,则事件的广播有效载荷为:
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}
但是,如果希望对广播的有效负载进行更细粒度的控制,则可以在事件中添加broadcastWith
方法。此方法应返回您希望广播的数据数组作为事件有效负载:
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith()
{
return ['id' => $this->user->id];
}
广播队列
默认情况下,每个广播事件都放置在queue.php配置文件中为默认队列连接指定的默认队列中。我们可以通过在broadcastQueue
事件类上定义属性来自定义广播者使用的队列。此属性应指定要在广播时使用的队列的名称:
/**
* The name of the queue on which to place the event.
*
* @var string
*/
public $broadcastQueue = 'your-queue-name';
如果要使用sync队列而不是默认队列驱动程序广播事件,则可以实现ShouldBroadcastNow接口:
<?php
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class ShippingStatusUpdated implements ShouldBroadcastNow
{
//
}
条件广播
有时,只想在给定条件为真的情况下广播事件。可以在事件类中通过添加broadcastWhen方法来定义这些条件:
/**
* Determine if this event should broadcast.
*
* @return bool
*/
public function broadcastWhen()
{
return $this->value > 100;
}
授权 Channel
私有频道要求我们授权当前经过身份验证的用户可以实际收听该频道。当使用Laravel Echo时,将自动发出用于授权对私有频道的订阅的HTTP请求。但是,需要定义适当的路由来响应这些请求。
定义授权路由
值得庆幸的是,Laravel使得定义对频道授权请求的路由进行响应变得容易。在BroadcastServiceProviderLaravel
中,我们将看到对Broadcast::routes
方法的调用。此方法将注册/broadcasting/auth
路由来处理授权请求:
Broadcast::routes();
Broadcast::routes
方法将自动把路由放入web中间件组中。但是,如果您要自定义分配的属性,则可以将路由属性数组传递给该方法:
Broadcast::routes($attriutes);
自定义授权端点
默认情况下,Echo将使用/broadcasting/auth
端点来授权通道访问。但是,可以通过将authEndpoint
配置选项传递给Echo实例来指定自己的授权端点:
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key',
authEndpoint: '/custom/endpoint/auth'
});
定义授权回调
接下来,我们需要定义将实际执行通道授权的逻辑。这是在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;
});
授权回调认证
专用和状态广播频道通过应用程序的默认身份验证保护程序来验证当前用户。如果用户未通过身份验证,则将自动拒绝频道授权,并且永远不会执行授权回调。但是,我们可以分配多个自定义防护措施,如果需要,这些防护措施应该对传入的请求进行身份验证:
Broadcast::channel('channel', function () {
// ...
}, ['guards' => ['web', 'admin']]);
定义频道类别
如果我们的应用程序使用许多不同的通道,则routes/channels.php
文件可能会变得很大。因此,可以使用频道类来代替使用Closures授权通道。要生成频道类,请使用Artisan命令make:channel
。此命令将在App/Broadcasting
目录中放置一个新的通道类。
$ php artisan make:channel OrderChannel
接下来,在routes/channels.php
文件中注册新生成的频道:
use App\Broadcasting\OrderChannel;
Broadcast::channel('order.{order}', OrderChannel::class);
最后,可以将频道的授权逻辑放置在channel类的join
方法中。join方法将包含与通常在通道授权关闭中放置的逻辑相同的逻辑。还可以利用频道模型绑定:
<?php
namespace App\Broadcasting;
use App\Order;
use App\User;
class OrderChannel
{
/**
* Create a new channel instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Authenticate the user's access to the channel.
*
* @param \App\User $user
* @param \App\Order $order
* @return array|bool
*/
public function join(User $user, Order $order)
{
return $user->id === $order->user_id;
}
}
与Laravel中的许多其他类一样,通道类将由service container自动解析。因此,我们可以在其构造函数中键入类型提示,以获取通道所需的任何依赖关系。
广播事件
定义事件并将其标记为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
实例时,会将套接字ID分配给该连接。如果使用的是Vue和Axios,套接字ID将自动作为X-Socket-ID
标头附加到每个传出请求。然后,当调用toOthers
方法时,Laravel将从标头中提取套接字ID,并指明不要向使用该套接字ID的任何连接广播。
如果不使用Vue和Axios,则需要手动配置JavaScript应用程序以发送X-Socket-ID标头。可以使下Echo.socketId
方法检索套接字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/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连接失败非TLS连接也会被创建):
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key',
cluster: 'eu',
forceTLS: true
});
使用现有的客户端实例
如果已经具有Pusher Channels或Socket.io客户端实例,则Echo可以使用这些实例。可以通过client配置选项将其传递给Echo :
const client = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-channels-key',
client: client
});
监听事件
安装并实例化Echo之后,就可以开始收听事件广播了。首先,使用channel
方法检索通道的实例,然后调用listen
方法以侦听指定的事件:
Echo.channel('orders')
.listen('OrderShipped', (e) => {
console.log(e.order.name);
});
如果想在私有频道上监听事件,请改用private
方法。您可以继续将listen方法的调用链接起来,以在单个通道上侦听多个事件:
Echo.private('orders')
.listen(...)
.listen(...)
.listen(...);
离开频道
要离开频道,可以在Echo实例上调用leaveChannel
方法:
Echo.leaveChannel('orders');
如果想离开某个频道及其相关的私有频道和状态频道,则可以调用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
的实例:
/**
* Get the channels the event should broadcast on.
*
* @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方法监听join事件:
Echo.join(`chat.${roomId}`)
.here(...)
.joining(...)
.leaving(...)
.listen('NewMessage', (e) => {
//
});
客户端事件
有时,我们可能希望将事件广播到其他已连接的客户端,而不用通过Laravel应用程序。这对于诸如“正在输入”通知之类的事情特别有用,在这种情况下,您要向应用程序的用户警告其他用户正在给定屏幕上键入消息。
要广播客户端事件,您可以使用Echo的whisper
方法:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
要监听客户端事件,可以使用以下listenForWhisper
方法:
Echo.private('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}
频道的频道授权回调。