{{TOC}}
{{DOCVER 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51}}
== Введение ==
Во многих современных веб-приложениях для реализации обновляющегося на лету пользовательского интерфейса, работающего в режиме реального времени, используются WebSockets. Когда какая-либо информация изменяется на сервере, обычно посылается сообщение через WebSocket-подключение для обработки на клиенте. Это обеспечивает более надёжную и эффективную альтернативу постоянному опросу вашего приложения о наличии изменений.
Для помощи в создании таких приложений Laravel обеспечивает простую настройку "вещания" ваших ((//docs/v5/events событий)) через WebSocket-подключение. Вещание ваших Laravel-событий позволяет использовать одинаковые названия событий в коде на стороне клиента и в клиентском JavaScript-приложении.
.(alert)
Перед погружением в вещание событий не забудьте ознакомиться с документацией по ((//docs/v5/events событиям и слушателям)).
=== Настройка ===
Все настройки вещания событий вашего приложения хранятся в файле %%(t)config/broadcasting.php%%. Laravel изначально поддерживает несколько драйверов вещания: ((https://pusher.com Pusher)), ((//docs/v5/redis Redis)) и драйвер %%(t)log%% для локальной разработки и отладки. Вдобавок, есть драйвер %%(t)null%%, позволяющий полностью отключить вещание. Для каждого из них есть пример настройки в файле %%(t)config/broadcasting.php%%.
**Сервис-провайдер вещания**
Перед вещанием событий вам сначала надо зарегистрировать %%(t)App\Providers\BroadcastServiceProvider%%. В свежем Laravel-приложении вам достаточно раскомментировать этот провайдер в массиве %%(t)providers%% в файле %%(t)config/app.php%%. Этот провайдер позволит вам зарегистрировать маршруты и обратные вызовы авторизации вещания.
**CSRF-токен**
Для ((#установка Laravel Echo)) будет необходим доступ к CSRF-токену текущей сессии. Echo получит токен из JavaScript-объекта %%(t)Laravel.csrfToken%%, если он доступен. Этот объект определён в макете %%(t)resources/views/layouts/app.blade.php%%, который создаётся при выполнении Artisan-команды %%(sh)make:auth%%. Если вы не используете этот шаблон, вы можете определить тег %%(t)meta%% в HTML-элементе %%(t)head%% вашего приложения:
%%(html)
%%
=== Требования драйверов ===
**Pusher**
Если вы вещаете события через ((https://pusher.com Pusher)), вам надо установить Pusher PHP SDK через менеджер пакетов Composer:
%%
composer require pusher/pusher-php-server
%%
Затем вам надо настроить ваши учётные данные Pusher в файле %%(t)config/broadcasting.php%%. В этом файле есть пример настройки Pusher, поэтому вы можете быстро указать свой ключ Pusher, секрет и ID приложения. Настройка %%(t)pusher%% в файле %%(t)config/broadcasting.php%% также позволяет вам указать дополнительные %%(t)options%%, поддерживаемые Pusher, такие как кластер:
%%
'options' => [
'cluster' => 'eu',
'encrypted' => true
],
%%
При использовании Pusher и ((#установка Laravel Echo)) вам надо указать %%(t)pusher%% в качестве желаемого вещателя при создании экземпляра Echo в вашем файле %%(t)resources/assets/js/bootstrap.js%%:
%%(JS)
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key'
});
%%
**Redis**
Если вы используете вещатель Redis, вам надо установить библиотеку Predis:
%%(sh)
composer require predis/predis
%%
Вещатель Redis будет вещать сообщения с помощью функции Redis издатель-подписчик, но вам надо дополнить его WebSocket-сервером, который сможет получать сообщения от Redis и вещать их в ваши WebSocket-каналы.
Когда вещатель Redis публикует событие, оно публикуется на имена каналов в зависимости от события, а его содержимое - закодированная JSON-строка с именем события, данными %%(t)data%% и пользователем, который сгенерировал ID сокета события (при наличии).
**Socket.IO**
Если вы хотите использовать вещатель Redis вместе с сервером Socket.IO, вам надо включить клиентскую JavaScript-библиотеку Socket.IO в HTML-элемент %%(t)head%% вашего приложения. После запуска сервер Socket.IO автоматически предоставляет клиентскую JavaScript-библиотеку на стандартном URL. Например, если вы запускаете сервер Socket.IO на том же домене, где находится ваше веб-приложение, вы можете обратиться к клиентской библиотеке вот так:
%%(html)
%%
Затем вам надо создать новый экземпляр Echo с коннектором %%(t)socket.io%% и с хостом %%(t)host%%.
%%(JS)
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
%%
И наконец, вам надо запустить совместимый сервер Socket.IO. В Laravel не включена реализация сервера Socket.IO, но в GitHub-репозитории ((https://github.com/tlaverdure/laravel-echo-server tlaverdure/laravel-echo-server)) есть созданный сообществом сервер Socket.IO.
**Требования очереди**
Перед вещанием событий вам также необходимо настроить и запустить ((//docs/v5/queues слушатель очереди)). Всё вещание событий происходит через задачи очереди, поэтому оно незначительно влияет на время отклика вашего приложения.
== Обзор концепции ==
Вещание событий Laravel позволяет вам вещать ваши Laravel-события со стороны сервера вашему клиентскому JavaScript-приложению используя подход к WebSockets на основе драйверов. Сейчас Laravel поставляется с драйверами ((https://pusher.com Pusher)) и Redis. События можно легко получать на стороне клиента, используя JavaScript-пакет ((#установка Laravel Echo)).
События вещаются на "каналы", которые могут быть публичными или приватными. Любой посетитель вашего приложения может подписаться на публичный канал без какой-либо аутентификации и авторизации, но чтобы подписаться на приватный канал, пользователь должен быть аутентифицирован и авторизован на прослушивание этого канала.
=== Использование примера приложения ===
Перед погружением во все компоненты вещания событий давайте сделаем поверхностный осмотр интернет-магазина в качестве примера. Мы пока не будем обсуждать детали настройки ((https://pusher.com Pusher)) и ((#установка Laravel Echo)), поскольку они будут рассмотрены в других разделах этой статьи.
Предположим, в нашем приложении есть страница, позволяющая пользователям просматривать статус доставки их заказов. И предположим, что при обработке приложением обновления статуса доставки возникает событие %%(t)ShippingStatusUpdated%%:
%%
event(new ShippingStatusUpdated($update));
%%
**Интерфейс %%(t)ShouldBroadcast%%**
Когда пользователь просматривает один из своих заказов, надо показывать ему обновления статуса, не требуя обновлять страницу. Вместо этого будем вещать обновления в приложение по мере их создания. Итак, нам надо отметить событие %%(t)ShippingStatusUpdated%% интерфейсом %%(t)ShouldBroadcast%%. Таким образом Laravel поймёт, что надо вещать это событие при его возникновении:
%%
update->order_id);
}
%%
**Авторизация каналов**
Запомните, пользователи должны быть авторизованы для прослушивания приватных каналов. Мы можем определить правила авторизации нашего канала в методе %%boot()%% сервис-провайдера %%(t)BroadcastServiceProvider%%. В данном примере нам надо проверить, что пользователь, пытающийся слушать приватный канал %%(t)order.1%%, является создателем заказа:
%%
Broadcast::channel('order.*', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
%%
Метод %%channel()%% принимает два аргумента: название канала и функцию обратного вызова, которая возвращает %%true%% или %%false%%, в зависимости от того, авторизован пользователь слушать канал или нет.
Все авторизационные функции обратного вызова получают первым аргументом текущего аутентифицированного пользователя, а последующими аргументами - все дополнительные подстановочные параметры. В данном примере мы используем символ %%*%% для указания, что часть %%ID%% в имени канала является подстановочной.
**Прослушивание вещания событий**
Затем, всё что нам остаётся - это слушать события в нашем JavaScript-приложении. Это можно делать с помощью Laravel Echo. Сначала мы используем метод %%private()%%, чтобы подписаться на приватный канал. Затем мы можем использовать метод %%listen()%%, чтобы слушать событие %%(t)ShippingStatusUpdated%%. По умолчанию все общедоступные (%%public%%) свойства события будут включены в вещание события:
%%
Echo.private(`order.${orderId}`)
.listen('ShippingStatusUpdated', (e) => {
console.log(e.update);
});
%%
== Определение событий для вещания ==
Чтобы сообщить Laravel, что данное событие должно вещаться, реализуйте интерфейс %%(t)Illuminate\Contracts\Broadcasting\ShouldBroadcast%% на классе события. Этот интерфейс уже импортирован во все классы событий, генерируемые фреймворком, поэтому вы легко можете добавить его в любое ваше событие.
Интерфейс %%(t)ShouldBroadcast%% требует от вас реализовать единственный метод %%broadcastOn()%%. Этот метод должен возвращать канал или массив каналов, на которые должно вещаться событие. Каналы должны быть экземплярами %%(t)Channel%%, %%(t)PrivateChannel%% или %%(t)PresenceChannel%%. Экземпляры %%(t)Channel%% представляют публичные каналы, на которые может подписаться любой пользователь, а %%(t)PrivateChannel%% и %%(t)PresenceChannel%% представляют приватные каналы, которые требуют ((#авторизация авторизации каналов)):
%%
user = $user;
}
/**
* Получить каналы, на которые надо вещать событие.
*
* @return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('user.'.$this->user->id);
}
}
%%
Затем вам надо только ((//docs/v5/events создать событие)) как обычно. После этого ((//docs/v5/queues задача в очереди)) автоматически проведёт вещание события через ваши драйверы вещания.
=== Название вещания ===
По умолчанию Laravel вещает событие, используя имя класса события. Но вы можете изменить название вещания, определив метод %%broadcastAs()%% на событии:
%%
/**
* Название вещания события.
*
* @return string
*/
public function broadcastAs()
{
return 'server.created';
}
%%
=== Данные вещания ===
При вещании события все его %%public%% свойства автоматически сериализуются и вещаются в качестве содержимого события, позволяя вам обращаться к любым общедоступным данным события из вашего JavaScript-приложения. Так например, если у вашего события есть единственное общедоступное свойство %%$user%%, которое содержит модель Eloquent, при вещании содержимым события будет:
%%
{
"user": {
"id": 1,
"name": "Patrick Stewart"
...
}
}
%%
Но если вы хотите иметь более точный контроль над содержимым вещания, вы можете добавить метод %%broadcastWith()%% в ваше событие. Этот метод должен возвращать массив данных, которые вы хотите вещать в качестве содержимого события:
%%
/**
* Получить данные для вещания.
*
* @return array
*/
public function broadcastWith()
{
return ['id' => $this->user->id];
}
%%
=== Очередь вещания ===
По умолчанию каждое событие для вещания помещается в очередь по умолчанию на подключении по умолчанию, указанном в вашем файле настроек %%(t)queue.php%%. Вы можете изменить используемую вещателем очередь, определив свойство %%broadcastQueue%% в классе вашего события. Это свойство должно указывать название очереди для вещания:
%%
/**
* Название очереди для размещения события.
*
* @var string
*/
public $broadcastQueue = 'your-queue-name';
%%
((#авторизация))
== Авторизация каналов ==
Приватные каналы требуют авторизации того, что текущий аутентифицированный пользователь действительно может слушать канал. Это достигается HTTP-запросом в ваше Laravel-приложение с названием канала, а приложение должно определить, может ли пользователь слушать этот канал. При использовании ((#установка Laravel Echo)) HTTP-запрос для авторизации подписки на приватные каналы делается автоматически, но вам надо определить необходимые маршруты, чтобы отвечать на эти запросы.
=== Определение маршрутов авторизации ===
К счастью, Laravel позволяет легко определить маршруты для ответа на запросы авторизации каналов. В %%(t)BroadcastServiceProvider%%, включённом в ваше Laravel-приложение, вы увидите вызов метода %%Broadcast::routes()%%. Этот метод зарегистрирует маршрут %%(t)/broadcasting/auth%% для обработки запросов авторизации:
%%
Broadcast::routes();
%%
Метод %%Broadcast::routes()%% автоматически поместит свои маршруты в группу посредников %%(t)web%%, но вы можете передать в метод массив атрибутов маршрута, если хотите изменить назначенные атрибуты:
%%
Broadcast::routes($attributes);
%%
=== Определение функций обратного вызова для авторизации ===
Затем нам надо определить логику, которая будет отвечать за саму авторизацию. Как и определение маршрутов авторизации, это делается в методе %%boot()%% в %%(t)BroadcastServiceProvider%%. В этом методе вы можете использовать метод %%Broadcast::channel()%% для регистрации функций обратного вызова для авторизации:
%%
Broadcast::channel('order.*', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
%%
Метод %%channel()%% принимает два аргумента: название канала и функцию обратного вызова, которая возвращает %%true%% или %%false%%, в зависимости о того, авторизован пользователь на прослушивание канала или нет.
Все авторизационные функции обратного вызова получают первым аргументом текущего аутентифицированного пользователя, а последующими аргументами - все дополнительные подстановочные параметры. В данном примере мы используем символ %%*%% для указания, что часть %%ID%% в имени канала является подстановочной.
== Вещание событий ==
Когда вы определили событие и отметили его интерфейсом %%(t)ShouldBroadcast%%, вам остаётся только создать событие функцией %%event()%%. Диспетчер события обнаружит, что оно отмечено интерфейсом %%(t)ShouldBroadcast%%, и поместит его в очередь для вещания:
%%
event(new ShippingStatusUpdated($update));
%%
=== Только другим ===
При создании приложения с использованием вещания событий вы можете заменить функцию %%event()%% функцией %%broadcast()%%. Как и функция %%event()%%, функция %%broadcast()%% отправляет событие вашим слушателям на стороне сервера:
%%
broadcast(new ShippingStatusUpdated($update));
%%
Но функция %%broadcast()%% также предоставляет метод %%toOthers()%%, который позволяет вам исключить текущего пользователя из списка получателей вещания:
%%
broadcast(new ShippingStatusUpdated($update))->toOthers();
%%
Для лучшего понимания того, когда вам может пригодиться метод %%toOthers()%%, давайте представим приложение со списком задач, в котором пользователь может создать новую задачу, указав её имя. Чтобы создать задачу, ваше приложение должно сделать запрос к конечной точке %%(t)/task%%, которая произведёт вещание создания задачи и вернёт JSON-представление новой задачи. Когда ваше JavaScript-приложение получит отклик от конечной точки, оно сможет вставить новую задачу прямо в список задач:
%%
this.$http.post('/task', task)
.then((response) => {
this.tasks.push(response.data);
});
%%
Но помните, что мы также вещаем создание события. Если ваше JavaScript-приложение слушает это событие, чтобы добавить задачу в список задач, мы получим дублирование задачи в списке: одна из конечной точки, а другая из вещания.
Этого можно избежать с помощью метода %%toOthers()%%, который укажет вещателю, что событие не надо вещать текущему пользователю.
**Настройка**
Когда вы инициализируете экземпляр Laravel Echo, на подключение назначается ID сокета. Если вы используете ((https://vuejs.org Vue)) и Vue Resource, ID сокета будет автоматически прикреплён к каждому исходящему запросу в виде заголовка %%(t)X-Socket-ID%%. Затем, когда вы вызываете метод %%toOthers()%%, Laravel извлечёт ID сокета из заголовка и даст вещателю команду - не вещать на любые подключения с этим ID сокета.
Если вы не используете Vue и Vue Resource, вам надо вручную настроить ваше JavaScript-приложение, чтобы оно посылало заголовок %%(t)X-Socket-ID%%. Вы можете получить ID сокета методом %%Echo.socketId()%%:
%%
var socketId = Echo.socketId();
%%
== Получение вещания ==
=== Установка Laravel Echo ===
Laravel Echo - JavaScript-библиотека, которая позволяет без труда подписываться на каналы и слушать вещание событий Laravel. Вы можете установить Echo через менеджер пакетов NPM. В данном примере мы также установим пакет %%(t)pusher-js%%, поскольку будем использовать вещатель Pusher:
%%
npm install --save laravel-echo pusher-js
%%
После установки Echo вы можете создать свежий экземпляр Echo в JavaScript своего приложения. Отличное место для этого - конец файла %%(t)resources/assets/js/bootstrap.js%%, который включён в фреймворк Laravel:
%%(JS)
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key'
});
%%
При создании экземпляра Echo, который использует коннектор %%(t)pusher%%, вы также можете указать %%(t)cluster%%, и надо ли шифровать подключение:
%%(JS)
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key',
cluster: 'eu',
encrypted: true
});
%%
=== Прослушивание событий ===
Когда вы установили и создали экземпляр Echo, вы можете начать слушать вещание событий. Сначала используйте метод %%channel()%% для получения экземпляра канала, а затем вызовите метод %%listen()%% для прослушивания указанного события:
%%(JS)
Echo.channel('orders')
.listen('OrderShipped', (e) => {
console.log(e.order.name);
});
%%
Если вы хотите слушать события на приватном канале, тогда используйте метод %%private()%%. Вы можете продолжить прицеплять вызовы метода %%listen()%%, чтобы слушать несколько событий на одном канале:
%%(JS)
Echo.private('orders')
.listen(...)
.listen(...)
.listen(...);
%%
=== Выход из канала ===
Чтобы выйти из канала, можете вызвать метод %%leave()%% на вашем экземпляре Echo:
%%(JS)
Echo.leave('orders');
%%
=== Пространства имён ===
В этих примерах вы могли заметить, что мы не указываем полное пространство имён для классов событий. Это потому, что Echo автоматически считает, что события расположены в пространстве имён %%(t)App\Events%%. Но вы может настроить корневое пространство имён при создании экземпляра Echo, передав параметр %%(t)namespace%%:
%%(JS)
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key',
namespace: 'App.Other.Namespace'
});
%%
В качестве альтернативы вы можете добавлять к классам событий префикс %%.%%, когда подписываетесь на них с помощью Echo. Это позволит вам всегда указывать полное имя класса:
%%(JS)
Echo.channel('orders')
.listen('.Namespace.Event.Class', (e) => {
//
});
%%
== Каналы присутствия ==
Каналы присутствия построены на безопасности приватных каналов, при этом они предоставляют дополнительные возможности по осведомлённости о тех, кто подписан на канал. Это позволяет легко создавать мощные функции приложения для совместной работы, такие как уведомление пользователей, когда другой пользователь просматривает ту же страницу.
=== Авторизация каналов присутствия ===
Все каналы присутствия также являются приватными каналами, поэтому пользователи должны быть ((#авторизация авторизованы для доступа к ним)). Но при определении функций обратного вызова для каналов присутствия вам не надо возвращать %%true%%, если пользователь авторизован на подключение к каналу. Вместо этого вы должны вернуть массив данных о пользователе.
Данные, возвращённые функцией обратного вызова для авторизации, станут доступны для слушателей событий канала присутствия в вашем JavaScript-приложении. Если пользователь не авторизован на подключение к каналу, вы должны вернуть %%false%% или %%null%%:
%%
Broadcast::channel('chat.*', function ($user, $roomId) {
if ($user->canJoinRoom($roomId)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
%%
=== Подключение к каналам присутствия ===
Для подключения к каналу присутствия вы можете использовать метод Echo %%join()%%. Метод %%join()%% вернёт реализацию %%(t)PresenceChannel%%, которая помимо предоставления метода %%listen()%% позволит вам подписаться на события %%(t)here%%, %%(t)joining%% и %%(t)leaving%%.
%%(JS)
Echo.join(`chat.${roomId}`)
.here((users) => {
//
})
.joining((user) => {
console.log(user.name);
})
.leaving((user) => {
console.log(user.name);
});
%%
Обратный вызов %%here()%% будет выполнен немедленно после успешного подключения к каналу и получит массив с информацией о пользователе для всех остальных пользователей, подключенных к каналу в данный момент. Метод %%joining()%% будет выполнен, когда новый пользователь подключится к каналу, а метод %%leaving()%% будет выполнен, когда пользователь покинет канал.
=== Вещание в канал присутствия ===
Канал присутствия может получать события так же, как публичный и приватный каналы. Используя пример с чатом, нам может понадобиться вещание событий %%(t)NewMessage%% в канал присутствия чата. Для этого мы будем возвращать экземпляр %%(t)PresenceChannel%% из метода события %%broadcastOn()%%:
%%
/**
* Получить каналы, на которые необходимо вещать событие.
*
* @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()%%:
%%(JS)
Echo.join(`chat.${roomId}`)
.here(...)
.joining(...)
.leaving(...)
.listen('NewMessage', (e) => {
//
});
%%
== Уведомления ==
Дополнив вещание событий ((//docs/v5/notifications уведомлениями)), вы позволите вашему JavaScript-приложению получать новые уведомления при их возникновении без необходимости обновлять страницу. Сначала прочитайте документацию по использованию ((//docs/v5/notifications#вещание канала вещания уведомлений)).
Когда вы настроите уведомления на использование канала вещания, вы сможете слушать события вещания с помощью метода Echo %%notification()%%. Запомните, название канала должно совпадать с именем класса той сущности, которая получает уведомления:
%%(JS)
Echo.private(`App.User.${userId}`)
.notification((notification) => {
console.log(notification.type);
});
%%
В этом примере все уведомления, посылаемые экземплярам %%(t)App\User%% через канал %%(t)broadcast%%, будут получены функцией обратного вызова. Функция обратного вызова для авторизации канала %%(t)App.User.*%% включена в стандартный %%(t)BroadcastServiceProvider%%, который поставляется с фреймворком Laravel.