{{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.