今天,我们将探索 Laravel Web 框架中广播的概念。它允许您在服务器端发生某些事情时向客户端发送通知。在本文中,我们将使用第三方 Pusher 库向客户端发送通知。
如果您曾经想在 Laravel 的服务器上发生某些事情时将通知从服务器发送到客户端,那么您正在寻找广播功能。
例如,假设您已经实现了一个消息传递应用程序,它允许您系统的用户相互发送消息。现在,当用户 A 向用户 B 发送消息时,您想实时通知用户 B。您可以显示一个弹出窗口或警告框,通知用户 B 新消息!
这是了解 Laravel 中广播概念的完美用例,这也是我们将在本文中实现的内容。
如果您想知道服务器如何向客户端发送通知,它在后台使用套接字来完成它。在深入了解实际实现之前,让我们了解套接字的基本流程。
首先,您需要一个支持 web-sockets 协议并允许客户端建立 web 套接字连接的服务器。
您可以实现自己的服务器或使用 Pusher 等第三方服务。在本文中,我们更喜欢后者。
客户端向 Web 套接字服务器发起 Web 套接字连接,并在连接成功时接收到唯一标识符。
一旦连接成功,客户端就会订阅它希望接收事件的某些频道。
最后,在订阅的频道下,客户端注册它想收听的事件。
现在在服务器端,当特定事件发生时,我们通过向 web-socket 服务器提供通道名称和事件名称来通知它。
最后,web-socket 服务器将该事件广播到该特定通道上的注册客户端。
如果一次看起来太多,请不要担心;当我们阅读本文时,您将掌握它的窍门。
广播配置文件
接下来,我们看一下config/broadcasting.php中的默认广播配置文件。
<?php return [ /* |-------------------------------------------------------------------------- | Default Broadcaster |-------------------------------------------------------------------------- | | This option controls the default broadcaster that will be used by the | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | | Supported: "pusher", "redis", "log", "null" | */ 'default' => env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'useTLS' => true, ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ], ];
默认情况下,Laravel 在核心本身支持多个广播适配器。
在本文中,我们将使用pusher
广播适配器。出于调试目的,您还可以使用log
适配器。当然,如果你使用log
适配器,客户端不会收到任何事件通知,它只会被记录到laravel.log文件中。
从下一节开始,我们将立即深入研究上述用例的实际实现。
设置先决条件
在广播中,有不同类型的频道——公共、私人和在场。当您想公开广播您的活动时,它是您应该使用的公共频道。相反,当您想将 rict 事件通知发送到某些私人频道时,使用私人频道。
在我们的用例中,我们希望在用户收到新消息时通知他们。为了有资格接收广播通知,用户必须登录。因此,在我们的例子中,我们需要使用私有频道。
核心认证功能
首先,您需要启用默认的 Laravel 身份验证系统,以便注册、登录等功能开箱即用。如果您不确定如何做到这一点,官方文档可以快速了解这一点。
Pusher SDK:安装和配置
由于我们将使用Pusher
第三方服务作为我们的 web-socket 服务器,因此您需要使用它创建一个帐户,并确保您在注册后拥有必要的 api 凭据。
接下来,我们需要安装 Pusher PHP SDK,以便我们的 Laravel 应用程序可以向 Pusher web-socket 服务器发送广播通知。
在您的 Laravel 应用程序根目录中,运行以下命令将其安装为 composer 包。
$composer require pusher/pusher-php-server "~3.0"
现在,让我们更改.env文件以启用pusher
适配器作为我们的默认广播驱动程序。
... ... BROADCAST_DRIVER=pusher PUSHER_APP_ID={YOUR_APP_ID} PUSHER_APP_KEY={YOUR_APP_KEY} PUSHER_APP_SECRET={YOUR_APP_SECRET} PUSHER_APP_CLUSTER={YOUR_APP_CLUSTER} ... ...
如您所见,我们已将默认广播驱动程序更改为pusher
. 您还需要首先配置您应该从 Pusher 帐户获得的其他选项。
最后,让我们通过删除以下行中的注释来启用config/app.php 中的广播服务。
App\Providers\BroadcastServiceProvider::class,
到目前为止,我们已经安装了特定于服务器的库。在下一节中,我们将介绍还需要安装的客户端库。
Pusher 和 Laravel Echo 库——安装和配置
在广播中,客户端的职责是订阅频道并监听想要的事件。在后台,它通过打开到 web-socket 服务器的新连接来完成它。
幸运的是,我们不需要实现任何复杂的javascript来实现它,因为 Laravel 已经提供了一个有用的客户端库 Laravel Echo,它可以帮助我们在客户端处理套接字。此外,它还支持我们将在本文中使用的 Pusher 服务。
你可以使用 NPM 包管理器安装Laravel Echo库。当然,如果您还没有node和 npm,则需要安装它们。其余的非常简单,如下面的代码片段所示。
$npm install laravel-echo
我们感兴趣的是你应该复制到public/echo.js的node_modules/laravel-echo/dist/echo.js文件。
至此,我们就完成了客户端库的设置。
后端文件设置
回想一下,我们谈论的是设置一个应用程序,它允许我们应用程序的用户相互发送消息。另一方面,当登录的用户收到来自其他用户的新消息时,我们会向他们发送广播通知。
在本节中,我们将创建实现我们正在寻找的用例所需的文件。
创建模型类
首先,让我们创建一个Message
模型来保存用户相互发送的消息。
php artisan make:model Message --migration
我们还需要在表中添加一些字段,例如to
、from
和。因此,让我们在运行 migrate 命令之前更改迁移文件database/migrations/XXXX_XX_XX_XXXXXX_create_messages_table.php 。message
messages
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateMessagesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('messages', function (Blueprint $table) { $table->increments('id'); $table->integer('from', FALSE, TRUE); $table->integer('to', FALSE, TRUE); $table->text('message'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('messages'); } }
现在,让我们运行migrate
命令,它会messages
在数据库中创建表。
$php artisan migrate
创建事件类
每当您想在 Laravel 中引发自定义事件时,都应该为该事件创建一个类。根据事件的类型,Laravel做出相应的反应并采取必要的行动。
如果事件是普通事件,Laravel 会调用相关的监听类。另一方面,如果事件是广播类型,Laravel 会将该事件发送到在config/broadcasting.php文件中配置的 web-socket 服务器。
当我们在示例中使用 Pusher 服务时,Laravel 会将事件发送到 Pusher 服务器。
让我们使用以下 artisan 命令来创建自定义事件类:NewMessageNotification
.
$php artisan make:event NewMessageNotification
这应该创建app/Events/NewMessageNotification.php文件。让我们用以下内容替换该文件的内容。
<?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\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use App\Message; class NewMessageNotification implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; public $message; /** * Create a new event instance. * * @return void */ public function __construct(Message $message) { $this->message = $message; } /** * Get the channels the event should broadcast on. * * @return Channel|array */ public function broadcastOn() { return new PrivateChannel('user.'.$this->message->to); } }
需要注意的重要一点是NewMessageNotification
该类实现了ShouldBroadcastNow
接口。因此,当我们引发一个事件时,Laravel 知道该事件应该被广播。
其实你也可以实现这个 ShouldBroadcast
接口,Laravel 会在事件队列中添加一个事件。当它有机会这样做时,它将由事件队列工作人员处理。在我们的例子中,我们想立即广播它,这就是我们使用该ShouldBroadcastNow
接口的原因。
在我们的例子中,我们想要显示用户收到的消息,因此我们Message
在构造函数参数中传递了模型。这样,数据将与事件一起传递。
接下来是broadcastOn
方法,它定义了将在其上广播事件的频道的名称。在我们的例子中,我们使用了私有频道,因为我们希望将事件广播限制为登录用户。
该$this->message->to
变量是指将向其广播事件的用户的 ID。因此,它有效地使频道名称像user.{USER_ID}
.
创建广播路由
在私有通道的情况下,客户端必须在与 web-socket 服务器建立连接之前验证自己。它确保在私有频道上广播的事件仅发送给经过身份验证的客户端。在我们的例子中,这意味着只有登录用户才能订阅我们的频道user.{USER_ID}
。
如果你使用 Laravel Echo 客户端库来订阅频道,那么你很幸运!它会自动处理身份验证部分,您只需要定义通道路由。
让我们继续在routes/channels.php文件中为我们的私人频道添加一个路由。
<?php /* |-------------------------------------------------------------------------- | Broadcast Channels |-------------------------------------------------------------------------- | | Here you may register all of the event broadcasting channels that your | application supports. The given channel authorization callbacks are | used to check if an authenticated user can listen to the channel. | */ Broadcast::channel('App.User.{id}', function ($user, $id) { return (int) $user->id === (int) $id; }); Broadcast::channel('user.{toUserId}', function ($user, $toUserId) { return $user->id == $toUserId; });
如您所见,我们已经user.{toUserId}
为我们的私人频道定义了路由。
通道方法的第二个参数应该是一个闭包函数。Laravel 自动将当前登录的用户作为闭包函数的第一个参数传递,第二个参数通常是从通道名称中获取的。
当客户端尝试订阅私有频道user.{USER_ID}
时,Laravel Echo 库使用XMLHttpRequest对象(通常称为 XHR)在后台进行必要的身份验证。
我们现在已经完成了设置,让我们继续测试它。
前端文件设置
在本节中,我们将创建测试用例所需的文件。
创建控制器
让我们继续在app/Http/Controllers/MessageController.php中创建一个控制器文件,其中包含以下内容。
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Message; use App\Events\NewMessageNotification; use Illuminate\Support\Facades\Auth; class MessageController extends Controller { public function __construct() { $this->middleware('auth'); } public function index() { $user_id = Auth::user()->id; $data = array('user_id' => $user_id); return view('broadcast', $data); } public function send() { // ... // message is being sent $message = new Message; $message->setAttribute('from', 1); $message->setAttribute('to', 2); $message->setAttribute('message', 'Demo message from user 1 to user 2'); $message->save(); // want to broadcast NewMessageNotification event event(new NewMessageNotification($message)); // ... } }
创建视图
在index
方法中,我们使用broadcast
视图,所以让我们也创建resources/views/broadcast.blade.php视图文件。
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>Test</title> <!-- Styles --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> <nav class="navbar navbar-default navbar-static-top"> <div> <div> <!-- Collapsed Hamburger --> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse"> <span>Toggle Navigation</span> <span></span> <span></span> <span></span> </button> <!-- Branding Image --> <a href="{{ url('/') }}"> Test </a> </div> <div class="collapse navbar-collapse" id="app-navbar-collapse"> <!-- Left Side Of Navbar --> <ul class="nav navbar-nav"> </ul> <!-- Right Side Of Navbar --> <ul class="nav navbar-nav navbar-right"> <!-- Authentication Links --> @if (Auth::guest()) <li><a href="{{ route('login') }}">Login</a></li> <li><a href="{{ route('register') }}">Register</a></li> @else <li> <a href="#" data-toggle="dropdown" role="button" aria-expanded="false"> {{ Auth::user()->name }} <span></span></a> <ul role="menu"> <li> <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> Logout </a> <form id="logout-form" action="{{ route('logout') }}" method="post" style="display: none;"> {{ csrf_field() }} </form> </li> </ul> </li> @endif </ul> </div></div></nav> <div> <div>New notification will be alerted realtime! </div> </div> </div> <!-- receive notifications --> <script src="{{ asset('js/echo.js') }}"></script> <script src="https://js.pusher.com/4.1/pusher.min.js"></script> <script> Pusher.logToConsole = true; window.Echo = new Echo({ broadcaster: 'pusher', key: 'c91c1b7e8c6ece46053b', cluster: 'ap2', encrypted: true, logToConsole: true }); Echo.private('user.{{ $user_id }}') .listen('NewMessageNotification', (e) => { alert(e.message.message); }); </script> <!-- receive notifications --> </body> </html>
添加路线
最后,我们还需要在routes/web.php文件中添加路由。
Route::get('message/index', 'MessageController@index'); Route::get('message/send', 'MessageController@send');
它是如何工作的
在控制器类的构造方法中,你可以看到我们使用了auth
中间件来确保控制器方法只有登录用户才能访问。
接下来是index
渲染broadcast
视图的方法。让我们拉入视图文件中最重要的代码。
<!-- receive notifications --> <script src="{{ asset('js/echo.js') }}"></script> <script src="https://js.pusher.com/4.1/pusher.min.js"></script> <script> Pusher.logToConsole = true; window.Echo = new Echo({ broadcaster: 'pusher', key: 'c91c1b7e8c6ece46053b', cluster: 'ap2', encrypted: true, logToConsole: true }); Echo.private('user.{{ $user_id }}') .listen('NewMessageNotification', (e) => { alert(e.message.message); }); </script> <!-- receive notifications -->
首先,我们加载必要的客户端库,Laravel Echo 和 Pusher,允许我们打开到 Pusher web-socket 服务器的 web-socket 连接。
pusher
接下来,我们通过提供广播适配器和其他必要的推送器相关信息来创建 Echo 实例。
进一步,我们使用private
Echo 的方法订阅私人频道user.{USER_ID}
。正如我们之前讨论的,客户端必须在订阅私有频道之前验证自己。因此,Echo
对象通过在后台发送带有必要参数的 XHR 来执行必要的身份验证。最后,Laravel 尝试找到user.{USER_ID}
路由,它应该匹配我们在routes/channels.php文件中定义的路由。
如果一切顺利,您应该与 Pusher web-socket 服务器打开一个 web-socket 连接,并且它会在user.{USER_ID}
频道上列出事件!从现在开始,我们将能够接收此频道上的所有传入事件。
在我们的例子中,我们想要监听NewMessageNotification
事件,因此我们使用listen
了对象的方法Echo
来实现它。为简单起见,我们只会提醒我们从 Pusher 服务器收到的消息。
这就是从 web-sockets 服务器接收事件的设置。接下来,我们将浏览send
控制器文件中引发广播事件的方法。
让我们快速拉入 send
方法的代码。
public function send() { // ... // message is being sent $message = new Message; $message->setAttribute('from', 1); $message->setAttribute('to', 2); $message->setAttribute('message', 'Demo message from user 1 to user 2'); $message->save(); // want to broadcast NewMessageNotification event event(new NewMessageNotification($message)); // ... }
在我们的例子中,我们将在登录用户收到新消息时通知他们。所以我们试图在send
方法中模仿这种行为。
接下来,我们使用了event
辅助函数来引发NewMessageNotification
事件。由于NewMessageNotification
事件是ShouldBroadcastNow
类型的,Laravel 从config/broadcasting.php文件加载默认的广播配置。最后,它将NewMessageNotification
事件广播到通道上配置的 web-socket 服务器user.{USER_ID}
。
在我们的例子中,事件将被广播到user.{USER_ID}
频道上的 Pusher web-socket 服务器。如果接收用户的 ID 是1
,则该事件将通过user.1
频道广播。
正如我们之前讨论的,我们已经有一个监听这个通道上的事件的设置,所以它应该能够接收这个事件,并且向用户显示警报框!
如何测试我们的设置
让我们继续了解您应该如何测试我们迄今为止构建的用例。
在浏览器中打开 URL https://your-laravel-site-domain/message/index 。如果您尚未登录,您将被重定向到登录屏幕。登录后,您应该会看到我们之前定义的广播视图——还没有什么花哨的。
事实上,Laravel 已经在后台为你做了很多工作。由于我们启用了Pusher.logToConsole
Pusher 客户端库提供的设置,它会在浏览器控制台中记录所有内容以进行调试。让我们看看当你访问http://your-laravel-site-domain/message/index时控制台记录了什么页面时控制台记录了哪些内容。
Pusher : State changed : initialized -> connecting Pusher : Connecting : {"transport":"ws","url":"wss://ws-ap2.pusher.com:443/app/c91c1b7e8c6ece46053b?protocol=7&client=js&version=4.1.0&flash=false"} Pusher : Connecting : {"transport":"xhr_streaming","url":"https://sockjs-ap2.pusher.com:443/pusher/app/c91c1b7e8c6ece46053b?protocol=7&client=js&version=4.1.0"} Pusher : State changed : connecting -> connected with new socket ID 1386.68660 Pusher : Event sent : {"event":"pusher:subscribe","data":{"auth":"c91c1b7e8c6ece46053b:cd8b924580e2cbbd2977fd4ef0d41f1846eb358e9b7c327d89ff6bdc2de9082d","channel":"private-user.2"}} Pusher : Event recd : {"event":"pusher_internal:subscription_succeeded","data":{},"channel":"private-user.2"} Pusher : No callbacks on private-user.2 for pusher:subscription_succeeded
它已经打开了与 Pusher web-socket 服务器的 web-socket 连接,并订阅了自己以监听私有通道上的事件。当然,根据您登录的用户 ID,您可以使用不同的频道名称。现在,让我们在开始测试时保持此页面打开send
方法时保持此页面打开。
接下来,让我们打开http://your-laravel-site-domain/message/send在另一个选项卡或其他浏览器中如果您要使用其他浏览器,则需要登录才能访问该页面。
一旦您打开http://your-laravel-site-domain/message/send页面,您应该能够在http://your-laravel-site-domain/message的另一个选项卡中看到一条警报消息/索引。
让我们导航到控制台,看看刚刚发生了什么。
Pusher : Event recd : {"event":"App\\Events\\NewMessageNotification","data": {"message": {"id":57,"from":1,"to":2,"message":"Demo message from user 1 to user 2","created_at": "2018-01-13 07:10:10","updated_at":"2018-01-13 07:10:10"}},"channel":"private-user.2"}
如您所见,它告诉您刚刚收到App\Events\NewMessageNotification
来自 Pusher web-socket 服务器的事件private-user.2
从频道
事实上,您也可以看到 Pusher 端正在发生的事情。转到您的 Pusher 帐户并导航到您的应用程序。在Debug Console下,您应该能够看到正在记录的消息。
这使我们到了本文的结尾!希望这不会太多,因为我已经尽我所知尽量简化事情。
结论
今天,我们讨论了 Laravel 中讨论最少的特性之一——广播。它允许您使用 Web 套接字发送实时通知。在本文的整个过程中,我们构建了一个展示上述概念的真实示例。
- 核心认证功能
- Pusher SDK:安装和配置
- Pusher 和 Laravel Echo 库——安装和配置
- 创建模型类
- 创建事件类
- 创建广播路由
- 创建控制器
- 创建视图
- 添加路线