Введение
Во многих современных веб-приложениях для реализации обновляющегося на лету пользовательского интерфейса, работающего в режиме реального времени, используются WebSockets. Когда какая-либо информация изменяется на сервере, обычно посылается сообщение через WebSocket-подключение для обработки на клиенте. Это обеспечивает более надёжную и эффективную альтернативу постоянному опросу вашего приложения о наличии изменений.
Для помощи в создании таких приложений Laravel обеспечивает простую настройку «вещания» ваших событий через WebSocket-подключение. Вещание ваших Laravel-событий позволяет использовать одинаковые названия событий в коде на стороне клиента и в клиентском JavaScript-приложении.
Перед погружением в вещание событий не забудьте ознакомиться с документацией по событиям и слушателям.
Настройка
Все настройки вещания событий вашего приложения хранятся в файле config/broadcasting.php. Laravel изначально поддерживает несколько драйверов вещания: Pusher, Redis и драйвер log для локальной разработки и отладки. Вдобавок, есть драйвер null, позволяющий полностью отключить вещание. Для каждого из них есть пример настройки в файле config/broadcasting.php.
Перед вещанием событий вам сначала надо зарегистрировать App\Providers\BroadcastServiceProvider. В свежем Laravel-приложении вам достаточно раскомментировать этот провайдер в массиве providers в файле config/app.php. Этот провайдер позволит вам зарегистрировать маршруты и обратные вызовы авторизации вещания.
Для Laravel Echo будет необходим доступ к CSRF-токену текущей сессии. Echo получит токен из JavaScript-объекта Laravel.csrfToken, если он доступен. Этот объект определён в макете resources/views/layouts/app.blade.php, который создаётся при выполнении Artisan-команды shmake:auth
. Если вы не используете этот шаблон, вы можете определить тег meta в HTML-элементе head вашего приложения:
xml<meta name="csrf-token" content="{{ csrf_token() }}">
Требования драйверов
Если вы вещаете события через Pusher, вам надо установить Pusher PHP SDK через менеджер пакетов Composer:
composer require pusher/pusher-php-server
Затем вам надо настроить ваши учётные данные Pusher в файле config/broadcasting.php. В этом файле есть пример настройки Pusher, поэтому вы можете быстро указать свой ключ Pusher, секрет и ID приложения. Настройка pusher в файле config/broadcasting.php также позволяет вам указать дополнительные options, поддерживаемые Pusher, такие как кластер:
'options' => [
'cluster' => 'eu',
'encrypted' => true
],
При использовании Pusher и Laravel Echo вам надо указать pusher в качестве желаемого вещателя при создании экземпляра Echo в вашем файле resources/assets/js/bootstrap.js:
jsimport Echo from "laravel-echo" window.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key' });
Если вы используете вещатель Redis, вам надо установить библиотеку Predis:
shcomposer require predis/predis
Вещатель Redis будет вещать сообщения с помощью функции Redis издатель-подписчик, но вам надо дополнить его WebSocket-сервером, который сможет получать сообщения от Redis и вещать их в ваши WebSocket-каналы.
Когда вещатель Redis публикует событие, оно публикуется на имена каналов в зависимости от события, а его содержимое — закодированная JSON-строка с именем события, данными data и пользователем, который сгенерировал ID сокета события (при наличии).
Если вы хотите использовать вещатель Redis вместе с сервером Socket.IO, вам надо включить клиентскую JavaScript-библиотеку Socket.IO в HTML-элемент head вашего приложения. После запуска сервер Socket.IO автоматически предоставляет клиентскую JavaScript-библиотеку на стандартном URL. Например, если вы запускаете сервер Socket.IO на том же домене, где находится ваше веб-приложение, вы можете обратиться к клиентской библиотеке вот так:
xml<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
Затем вам надо создать новый экземпляр Echo с коннектором socket.io и с хостом host.
jsimport Echo from "laravel-echo" window.Echo = new Echo({ broadcaster: 'socket.io', host: window.location.hostname + ':6001' });
И наконец, вам надо запустить совместимый сервер Socket.IO. В Laravel не включена реализация сервера Socket.IO, но в GitHub-репозитории tlaverdure/laravel-echo-server есть созданный сообществом сервер Socket.IO.
Перед вещанием событий вам также необходимо настроить и запустить слушатель очереди. Всё вещание событий происходит через задачи очереди, поэтому оно незначительно влияет на время отклика вашего приложения.
Обзор концепции
Вещание событий Laravel позволяет вам вещать ваши Laravel-события со стороны сервера вашему клиентскому JavaScript-приложению используя подход к WebSockets на основе драйверов. Сейчас Laravel поставляется с драйверами Pusher и Redis. События можно легко получать на стороне клиента, используя JavaScript-пакет Laravel Echo.
События вещаются на «каналы», которые могут быть публичными или приватными. Любой посетитель вашего приложения может подписаться на публичный канал без какой-либо аутентификации и авторизации, но чтобы подписаться на приватный канал, пользователь должен быть аутентифицирован и авторизован на прослушивание этого канала.
Использование примера приложения
Перед погружением во все компоненты вещания событий давайте сделаем поверхностный осмотр интернет-магазина в качестве примера. Мы пока не будем обсуждать детали настройки Pusher и Laravel Echo, поскольку они будут рассмотрены в других разделах этой статьи.
Предположим, в нашем приложении есть страница, позволяющая пользователям просматривать статус доставки их заказов. И предположим, что при обработке приложением обновления статуса доставки возникает событие ShippingStatusUpdated:
event(new ShippingStatusUpdated($update));
Когда пользователь просматривает один из своих заказов, надо показывать ему обновления статуса, не требуя обновлять страницу. Вместо этого будем вещать обновления в приложение по мере их создания. Итак, нам надо отметить событие ShippingStatusUpdated интерфейсом ShouldBroadcast. Таким образом 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
{
//
}
Интерфейс ShouldBroadcast требует, чтобы в событии был определён метод PHPbroadcastOn()
. Этот метод отвечает за возврат каналов, на которые надо вещать событие. В генерируемых классах событий уже определена пустая заготовка этого метода, нам остаётся только заполнить её. Нам надо, чтобы у создателя заказа была возможность просмотреть обновления статуса, поэтому мы будем вещать событие на приватный канал, привязанный к заказу:
/**
* Получить каналы, на которые надо вещать событие.
*
* @return array
*/
public function broadcastOn()
{
return new PrivateChannel('order.'.$this->update->order_id);
}
Запомните, пользователи должны быть авторизованы для прослушивания приватных каналов. Мы можем определить правила авторизации нашего канала в методе PHPboot()
сервис-провайдера BroadcastServiceProvider. В данном примере нам надо проверить, что пользователь, пытающийся слушать приватный канал order.1, является создателем заказа:
Broadcast::channel('order.*', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
Метод PHPchannel()
принимает два аргумента: название канала и функцию обратного вызова, которая возвращает PHPtrue
или PHPfalse
, в зависимости от того, авторизован пользователь слушать канал или нет.
Все авторизационные функции обратного вызова получают первым аргументом текущего аутентифицированного пользователя, а последующими аргументами — все дополнительные подстановочные параметры. В данном примере мы используем символ PHP*
для указания, что часть PHPID
в имени канала является подстановочной.
Затем, всё что нам остаётся — это слушать события в нашем JavaScript-приложении. Это можно делать с помощью Laravel Echo. Сначала мы используем метод PHPprivate()
, чтобы подписаться на приватный канал. Затем мы можем использовать метод PHPlisten()
, чтобы слушать событие ShippingStatusUpdated. По умолчанию все общедоступные (PHPpublic
) свойства события будут включены в вещание события:
Echo.private(`order.${orderId}`)
.listen('ShippingStatusUpdated', (e) => {
console.log(e.update);
});
Определение событий для вещания
Чтобы сообщить Laravel, что данное событие должно вещаться, реализуйте интерфейс Illuminate\Contracts\Broadcasting\ShouldBroadcast на классе события. Этот интерфейс уже импортирован во все классы событий, генерируемые фреймворком, поэтому вы легко можете добавить его в любое ваше событие.
Интерфейс ShouldBroadcast требует от вас реализовать единственный метод PHPbroadcastOn()
. Этот метод должен возвращать канал или массив каналов, на которые должно вещаться событие. Каналы должны быть экземплярами Channel, PrivateChannel или PresenceChannel. Экземпляры Channel представляют публичные каналы, на которые может подписаться любой пользователь, а PrivateChannel и PresenceChannel представляют приватные каналы, которые требуют авторизации каналов:
<?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 вещает событие, используя имя класса события. Но вы можете изменить название вещания, определив метод PHPbroadcastAs()
на событии:
/**
* Название вещания события.
*
* @return string
*/
public function broadcastAs()
{
return 'server.created';
}
Данные вещания
При вещании события все его PHPpublic
свойства автоматически сериализуются и вещаются в качестве содержимого события, позволяя вам обращаться к любым общедоступным данным события из вашего JavaScript-приложения. Так например, если у вашего события есть единственное общедоступное свойство PHP$user
, которое содержит модель Eloquent, при вещании содержимым события будет:
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}
Но если вы хотите иметь более точный контроль над содержимым вещания, вы можете добавить метод PHPbroadcastWith()
в ваше событие. Этот метод должен возвращать массив данных, которые вы хотите вещать в качестве содержимого события:
/**
* Получить данные для вещания.
*
* @return array
*/
public function broadcastWith()
{
return ['id' => $this->user->id];
}
Очередь вещания
По умолчанию каждое событие для вещания помещается в очередь по умолчанию на подключении по умолчанию, указанном в вашем файле настроек queue.php. Вы можете изменить используемую вещателем очередь, определив свойство PHPbroadcastQueue
в классе вашего события. Это свойство должно указывать название очереди для вещания:
/**
* Название очереди для размещения события.
*
* @var string
*/
public $broadcastQueue = 'your-queue-name';
Авторизация каналов
Приватные каналы требуют авторизации того, что текущий аутентифицированный пользователь действительно может слушать канал. Это достигается HTTP-запросом в ваше Laravel-приложение с названием канала, а приложение должно определить, может ли пользователь слушать этот канал. При использовании Laravel Echo HTTP-запрос для авторизации подписки на приватные каналы делается автоматически, но вам надо определить необходимые маршруты, чтобы отвечать на эти запросы.
Определение маршрутов авторизации
К счастью, Laravel позволяет легко определить маршруты для ответа на запросы авторизации каналов. В BroadcastServiceProvider, включённом в ваше Laravel-приложение, вы увидите вызов метода PHPBroadcast::routes()
. Этот метод зарегистрирует маршрут /broadcasting/auth для обработки запросов авторизации:
Broadcast::routes();
Метод PHPBroadcast::routes()
автоматически поместит свои маршруты в группу посредников web, но вы можете передать в метод массив атрибутов маршрута, если хотите изменить назначенные атрибуты:
Broadcast::routes($attributes);
Определение функций обратного вызова для авторизации
Затем нам надо определить логику, которая будет отвечать за саму авторизацию. Как и определение маршрутов авторизации, это делается в методе PHPboot()
в BroadcastServiceProvider. В этом методе вы можете использовать метод PHPBroadcast::channel()
для регистрации функций обратного вызова для авторизации:
Broadcast::channel('order.*', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
Метод PHPchannel()
принимает два аргумента: название канала и функцию обратного вызова, которая возвращает PHPtrue
или PHPfalse
, в зависимости о того, авторизован пользователь на прослушивание канала или нет.
Все авторизационные функции обратного вызова получают первым аргументом текущего аутентифицированного пользователя, а последующими аргументами — все дополнительные подстановочные параметры. В данном примере мы используем символ PHP*
для указания, что часть PHPID
в имени канала является подстановочной.
Вещание событий
Когда вы определили событие и отметили его интерфейсом ShouldBroadcast, вам остаётся только создать событие функцией PHPevent()
. Диспетчер события обнаружит, что оно отмечено интерфейсом ShouldBroadcast, и поместит его в очередь для вещания:
event(new ShippingStatusUpdated($update));
Только другим
При создании приложения с использованием вещания событий вы можете заменить функцию PHPevent()
функцией PHPbroadcast()
. Как и функция PHPevent()
, функция PHPbroadcast()
отправляет событие вашим слушателям на стороне сервера:
broadcast(new ShippingStatusUpdated($update));
Но функция PHPbroadcast()
также предоставляет метод PHPtoOthers()
, который позволяет вам исключить текущего пользователя из списка получателей вещания:
broadcast(new ShippingStatusUpdated($update))->toOthers();
Для лучшего понимания того, когда вам может пригодиться метод PHPtoOthers()
, давайте представим приложение со списком задач, в котором пользователь может создать новую задачу, указав её имя. Чтобы создать задачу, ваше приложение должно сделать запрос к конечной точке /task, которая произведёт вещание создания задачи и вернёт JSON-представление новой задачи. Когда ваше JavaScript-приложение получит отклик от конечной точки, оно сможет вставить новую задачу прямо в список задач:
this.$http.post('/task', task)
.then((response) => {
this.tasks.push(response.data);
});
Но помните, что мы также вещаем создание события. Если ваше JavaScript-приложение слушает это событие, чтобы добавить задачу в список задач, мы получим дублирование задачи в списке: одна из конечной точки, а другая из вещания.
Этого можно избежать с помощью метода PHPtoOthers()
, который укажет вещателю, что событие не надо вещать текущему пользователю.
Когда вы инициализируете экземпляр Laravel Echo, на подключение назначается ID сокета. Если вы используете Vue и Vue Resource, ID сокета будет автоматически прикреплён к каждому исходящему запросу в виде заголовка X-Socket-ID. Затем, когда вы вызываете метод PHPtoOthers()
, Laravel извлечёт ID сокета из заголовка и даст вещателю команду — не вещать на любые подключения с этим ID сокета.
Если вы не используете Vue и Vue Resource, вам надо вручную настроить ваше JavaScript-приложение, чтобы оно посылало заголовок X-Socket-ID. Вы можете получить ID сокета методом PHPEcho.socketId()
:
var socketId = Echo.socketId();
Получение вещания
Установка Laravel Echo
Laravel Echo — JavaScript-библиотека, которая позволяет без труда подписываться на каналы и слушать вещание событий Laravel. Вы можете установить Echo через менеджер пакетов NPM. В данном примере мы также установим пакет pusher-js, поскольку будем использовать вещатель Pusher:
npm install --save laravel-echo pusher-js
После установки Echo вы можете создать свежий экземпляр Echo в JavaScript своего приложения. Отличное место для этого — конец файла resources/assets/js/bootstrap.js, который включён в фреймворк Laravel:
jsimport Echo from "laravel-echo" window.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key' });
При создании экземпляра Echo, который использует коннектор pusher, вы также можете указать cluster, и надо ли шифровать подключение:
jswindow.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key', cluster: 'eu', encrypted: true });
Прослушивание событий
Когда вы установили и создали экземпляр Echo, вы можете начать слушать вещание событий. Сначала используйте метод PHPchannel()
для получения экземпляра канала, а затем вызовите метод PHPlisten()
для прослушивания указанного события:
jsEcho.channel('orders') .listen('OrderShipped', (e) => { console.log(e.order.name); });
Если вы хотите слушать события на приватном канале, тогда используйте метод PHPprivate()
. Вы можете продолжить прицеплять вызовы метода PHPlisten()
, чтобы слушать несколько событий на одном канале:
jsEcho.private('orders') .listen(...) .listen(...) .listen(...);
Выход из канала
Чтобы выйти из канала, можете вызвать метод PHPleave()
на вашем экземпляре Echo:
jsEcho.leave('orders');
Пространства имён
В этих примерах вы могли заметить, что мы не указываем полное пространство имён для классов событий. Это потому, что Echo автоматически считает, что события расположены в пространстве имён App\Events. Но вы может настроить корневое пространство имён при создании экземпляра Echo, передав параметр namespace:
jswindow.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key', namespace: 'App.Other.Namespace' });
В качестве альтернативы вы можете добавлять к классам событий префикс PHP.
, когда подписываетесь на них с помощью Echo. Это позволит вам всегда указывать полное имя класса:
jsEcho.channel('orders') .listen('.Namespace.Event.Class', (e) => { // });
Каналы присутствия
Каналы присутствия построены на безопасности приватных каналов, при этом они предоставляют дополнительные возможности по осведомлённости о тех, кто подписан на канал. Это позволяет легко создавать мощные функции приложения для совместной работы, такие как уведомление пользователей, когда другой пользователь просматривает ту же страницу.
Авторизация каналов присутствия
Все каналы присутствия также являются приватными каналами, поэтому пользователи должны быть авторизованы для доступа к ним. Но при определении функций обратного вызова для каналов присутствия вам не надо возвращать PHPtrue
, если пользователь авторизован на подключение к каналу. Вместо этого вы должны вернуть массив данных о пользователе.
Данные, возвращённые функцией обратного вызова для авторизации, станут доступны для слушателей событий канала присутствия в вашем JavaScript-приложении. Если пользователь не авторизован на подключение к каналу, вы должны вернуть PHPfalse
или PHPnull
:
Broadcast::channel('chat.*', function ($user, $roomId) {
if ($user->canJoinRoom($roomId)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
Подключение к каналам присутствия
Для подключения к каналу присутствия вы можете использовать метод Echo PHPjoin()
. Метод PHPjoin()
вернёт реализацию PresenceChannel, которая помимо предоставления метода PHPlisten()
позволит вам подписаться на события here, joining и leaving.
jsEcho.join(`chat.${roomId}`) .here((users) => { // }) .joining((user) => { console.log(user.name); }) .leaving((user) => { console.log(user.name); });
Обратный вызов PHPhere()
будет выполнен немедленно после успешного подключения к каналу и получит массив с информацией о пользователе для всех остальных пользователей, подключенных к каналу в данный момент. Метод PHPjoining()
будет выполнен, когда новый пользователь подключится к каналу, а метод PHPleaving()
будет выполнен, когда пользователь покинет канал.
Вещание в канал присутствия
Канал присутствия может получать события так же, как публичный и приватный каналы. Используя пример с чатом, нам может понадобиться вещание событий NewMessage в канал присутствия чата. Для этого мы будем возвращать экземпляр PresenceChannel из метода события PHPbroadcastOn()
:
/**
* Получить каналы, на которые необходимо вещать событие.
*
* @return Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('room.'.$this->message->room_id);
}
Подобно публичным и приватным событиям, события канала присутствия можно вещать с помощью функции PHPbroadcast()
. Как и с другими событиями вы можете использовать метод PHPtoOthers()
, чтобы исключить текущего пользователя из списка получателей вещания:
broadcast(new NewMessage($message));
broadcast(new NewMessage($message))->toOthers();
Вы можете слушать событие подключения методом Echo PHPlisten()
:
jsEcho.join(`chat.${roomId}`) .here(...) .joining(...) .leaving(...) .listen('NewMessage', (e) => { // });
Уведомления
Дополнив вещание событий уведомлениями, вы позволите вашему JavaScript-приложению получать новые уведомления при их возникновении без необходимости обновлять страницу. Сначала прочитайте документацию по использованию канала вещания уведомлений.
Когда вы настроите уведомления на использование канала вещания, вы сможете слушать события вещания с помощью метода Echo PHPnotification()
. Запомните, название канала должно совпадать с именем класса той сущности, которая получает уведомления:
jsEcho.private(`App.User.${userId}`)
.notification((notification) => {
console.log(notification.type);
});
В этом примере все уведомления, посылаемые экземплярам App\User через канал broadcast, будут получены функцией обратного вызова. Функция обратного вызова для авторизации канала App.User.* включена в стандартный BroadcastServiceProvider, который поставляется с фреймворком Laravel.