Может войдёшь?
Черновики Написать статью Профиль

Знакомство с Laravel Echo: подробный разбор

перевод новое в 5.3

  1. 1. Когда мне пригодится Echo?
  2. 2. Перед началом работы с Echo: настройка примера рассылки события в Laravel
  3. 3. Echo
    1. 3.1. Установка JS-библиотеки Echo
    2. 3.2. Подписка на общедоступные каналы с помощью Echo
  4. 4. Подписка на частные каналы с помощью Echo
    1. 4.1. Основы аутентификации и авторизации Echo
      1. 4.1.1. Создание прав доступа для нашего частного канала
  5. 5. Подписка на каналы присутствия с помощью Echo
  6. 6. Исключение текущего пользователя
  7. 7. Вот и всё!

С помощью инструмента Laravel Echo вы легко сможете использовать мощь WebSockets в своих Laravel-приложениях. Он упрощает самые необходимые и самые трудные аспекты построения сложных взаимодействий WebSockets.

Echo состоит из двух частей: набора улучшений для системы вещания сообщений Laravel (Event broadcasting system), и нового пакета JavaScript.

Бэкендовые компоненты Echo уже встроены в ядро Laravel, начиная с версии 5.3, их не надо импортировать (в этом их отличие от таких компонентов, как Cashier). Вы можете использовать эти бэкендовые улучшения с любым JavaScript-фронтендом, а не только с JavaScript-библиотекой Echo, и при этом всё равно получите значительные упрощение работы с WebSockets. Но с JavaScript-библиотекой Echo они работают ещё лучше.

JavaScript-библиотеку Echo можно импортировать через NPM, а затем импортировать в JavaScript вашего приложения. Это слой «сахара» поверх Pusher JS (JavaScript SDK для Pusher), либо поверх Socket.io (многие используют этот JavaScript SDK поверх архитектуры Redis WebSockets).

Когда мне пригодится Echo?

Перед тем, как пойти дальше, давайте рассмотрим, как вы можете использовать Echo, чтобы понять, нужно ли вам это.

Вам будут полезны WebSockets, если вы хотите посылать сообщения вашим пользователям — будь то уведомления или даже обновления структуры данных страницы — пока пользователи находятся на одной странице. Да, вы можете реализовать это с помощью длинных опросов (long-polling), или какого-нибудь регулярного пинга JavaScript, но при этом вы рискуете очень быстро уронить свой сервер. WebSockets мощны, не перегружают ваши сервера, легко масштабируются, и почти мгновенны.

Если вы хотите использовать WebSockets в Laravel-приложении, то Echo обеспечивает приятный, чистый синтаксис для простых функций, таких как общедоступные каналы, и сложных функций, таких как аутентификация, авторизация, частные каналы и каналы присутствия.

Важный нюанс перед началом работы: реализация WebSockets предоставляет три типа каналов: public — общедоступные — подписаться может каждый; private — частные — фронтенд должен аутентифицировать пользователя для бэкенда и убедиться, что у пользователя есть право на подписку на этот канал; presence — каналы присутствия — которые не позволяют отправку сообщений, а только уведомляют, присутствует ли пользователь на канале.

Перед началом работы с Echo: настройка примера рассылки события в Laravel

Предположим, вы хотите создать систему чатов с множеством комнат. Амбициозно, правда? Тогда нам надо генерировать событие каждый раз, когда приходит новое сообщение в чат.

Для полноценного понимания этой статьи, вы должны быть знакомы с рассылкой событий Laravel. Сначала стоит прочесть моё краткое введение в рассылку событий.

Итак, сначала создадим событие:

shphp artisan make:event ChatMessageWasReceived

Откройте этот класс (app/Events/ChatMessageWasReceived.php) и пометьте его как реализующий интерфейс PHPShouldBroadcast. А теперь давайте просто сделаем его рассылку в канал с названием "chat-room.1".

В версии 5.3 у метода PHPbroadcastOn() новая структура, которая освобождает вас от необходимости определять частные каналы и каналы присутствия с помощью префиксов «private-» и «presence-». Теперь вы можете просто обернуть имя канала в простой объект PHPPrivateChannel или PHPPresenceChannel. Итак, чтобы сделать рассылку в общедоступный канал — PHPreturn "chat-room.1";. Для рассылки в частный канал — PHPreturn new PrivateChannel("chat-room.1");. А для рассылки в канал присутствия — PHPreturn new PresenceChannel("chat-room.1");.

Вероятно, вам понадобится создать модель и миграцию для PHPChatMessage, и создать в ней поля user_id и message.

shphp artisan make:model ChatMessage --migration

Вот пример миграции:

PHP
...
class 
CreateChatMessagesTable extends Migration
{
    public function 
up()
    {
        
Schema::create('chat_messages', function (Blueprint $table) {
            
$table->increments('id');
            
$table->string('message');
            
$table->integer('user_id')->unsigned();
            
$table->timestamps();
        });
    }

    public function 
down()
    {
        
Schema::drop('chat_messages');
    }
}

А теперь давайте обновим наше событие, чтобы внедрить пользователя и сообщение:

PHP
...
class 
ChatMessageWasReceived extends Event implements ShouldBroadcast
{
    use 
InteractsWithSocketsSerializesModels;

    public 
$chatMessage;
    public 
$user;

    public function 
__construct($chatMessage$user)
    {
        
$this->chatMessage $chatMessage;
        
$this->user $user;
    }

    public function 
broadcastOn()
    {
        return [
            
"chat-room.1"
        
];
    }
}

И сделаем наши поля заполняемыми в модели:

PHP
...
class 
ChatMessage extends Model
{
    public 
$fillable = ['user_id''message'];
}

Теперь создадим способ для вызова этого события. Для тестирования я часто создаю Artisan-команду для вызова своих событий. Давайте попробуем.

shphp artisan make:command SendChatMessage

Откройте этот файл app/Console/Commands/SendChatMessage.php. Задайте для него описание, позволяющее вам передать в него сообщение, а затем добавьте в метод PHPhandle() вызов нашего события PHPChatMessageWasReceived с этим сообщением:

PHP
...
class 
SendChatMessage extends Command
{
    protected 
$signature 'chat:message {message}';

    protected 
$description 'Send chat message.';

    public function 
handle()
    {
        
// Вызов события, пока просто выбирая первого пользователя
        
$user = \App\User::first();
        
$message = \App\ChatMessage::create([
            
'user_id' => $user->id,
            
'message' => $this->argument('message')
        ]);

        
event(new \App\Events\ChatMessageWasReceived($message$user));
    }
}

Теперь откройте app/Console/Kernel.php и добавьте имя класса этой команды в свойство PHP$commands, чтобы зарегистрировать её как переменную Artisan-команду.

PHP
...
class 
Kernel extends ConsoleKernel
{
    protected 
$commands = [
        
Commands\SendChatMessage::class,
    ];
...

Почти готово! Наконец, вам надо зарегистрировать аккаунт в Pusher (Echo также работает с Redis и Socket.io, но для примера мы используем Pusher). Создайте новое приложение в своём Pusher-аккаунте и скопируйте key, secret и App ID. Теперь задайте эти значения в своём файле .env в PUSHER_KEY, PUSHER_SECRET и PUSHER_APP_ID. И ещё, пока вы здесь, задайте параметру BROADCAST_DRIVER значение pusher.

И наконец, запросите библиотеку Pusher:

shcomposer require pusher/pusher-php-server:~2.0

Теперь вы можете посылать события в ваш Pusher-аккаунт выполняя такие команды:

shphp artisan chat:message "Всем приветики"

Если всё сработает правильно, вы сможете войти в свою отладочную консоль в Pusher, вызвать это событие и увидеть такое:

/packages/proger/habravel/uploads/655-pusher-debug.png

Echo

Теперь у вас есть простая система для отправки событий в Pusher. Посмотрим, что даёт нам Echo.

Установка JS-библиотеки Echo

Простейший способ установить JavaScript-библиотеку Echo в ваш проект — импортировать её с помощью NPM и Elixir. Давайте сначала импортируем их самих и Pusher JS:

sh# Установка основнхых зависимостей Elixir
npm install
# Установка Pusher JS и Echo, и добавление в package.json
npm install --save laravel-echo pusher-js

Затем настроим resouces/assets/js/app.js для импорта:

jsimport Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'здесь-ваш-ключ-pusher'
});

// @todo: настроить привязки Echo здесь

Наконец, запустите shgulp или shgulp watch и не забудьте привязать файл вывода результатов к своему HTML-шаблону.

Если вы используете чистую установку Laravel, то выполните shphp artisan make:auth вместо того, чтобы писать весь HTML вручную. Для последующих функций всё равно потребуется аутентификация, поэтому сделаем её сейчас.

Echo нужен доступ к вашим CSRF-токенам. Если вы используете начальную загрузку авторизации Laravel, то она сделает токены доступными для Echo через Laravel.csrfToken. А если не используете, то можете сделать это сами, создав мета-тег csrf-token:

xml<html>
    <head>
        ...
        <meta name="csrf-token" content="{{ csrf_token() }}">
        ...
    </head>
    <body>
        ...

        <script src="js/app.js"></script>
    </body>
</html>

Фантастика! Давайте изучим синтаксис.

Подписка на общедоступные каналы с помощью Echo

Давайте вернёмся к resources/assets/js/app.js и послушаем общедоступный канал PHPchat-room.1, в который мы вещаем наше событие, и запишем все сообщения, пришедшие в пользовательскую консоль:

PHP
import EchoLibrary from "laravel-echo"

window.Echo = new EchoLibrary({
    
broadcaster'pusher',
    
key'здесь-ваш-ключ-pusher'
});

Echo.
channel('chat-room.1')
    .
listen('ChatMessageWasReceived', (e) => {
        
console.log(e.usere.chatMessage);
    });

Мы сказали Echo: подпишись на общедоступный канал PHPchat-room.1. Слушай событие PHPChatMessageWasReceived (обратите внимание, что Echo не требует указания полного пространства имён). И когда получишь это событие, передай его в эту анонимную функцию и выполни её.

Теперь посмотрим в нашу консоль:

/packages/proger/habravel/uploads/655-echo-public-console-log.png

Вжух! Всего несколько строк кода, и у нас есть полный доступ к JSON-представлениям нашего сообщения и нашего пользователя. Великолепно! Мы можем использовать эти данные не только для отправки сообщений пользователям, но и для изменения данных в системах хранения «в памяти» в вашем приложении (VueJS, React, или другой), позволяя каждому WebSocket сообщению изменять содержимое текущей страницы.

Теперь давайте перейдём к частным каналам и каналам присутствия, для которых необходимо использование аутентификации и авторизации.

Подписка на частные каналы с помощью Echo

Давайте сделаем PHPchat-room.1 частным. Сначала нам надо добавить PHPprivate- к названию канала. Отредактируйте метод PHPbroadcastsOn() нашего Laravel-события PHPChatMessageWasReceived, чтобы название канала было PHPprivate-chat-room.1. Или, для упрощения, вы можете передать название канала в новый экземпляр PHPPrivateChannel, который делает то же самое: PHPreturn new PrivateChannel('chat-room.1');.

Затем мы используем PHPEcho.private() в app.js вместо PHPEcho.channel().

Всё остальное можно оставить без изменений. Но если вы попытаетесь запустить скрипт, то заметите, что он не работает, а в консоли увидите такую ошибку:

/packages/proger/habravel/uploads/655-echo-auth-not-found.png

Это намёк на следующую большую функцию, которую обрабатывает Echo: аутентификация и авторизация.

Основы аутентификации и авторизации Echo

В системе авторизации две части. Первая — когда вы открываете своё приложение впервые, Echo хочет выполнить запрос POST к вашему маршруту /broadcasting/auth. Поскольку мы уже настроили инструменты Echo на стороне Laravel, этот маршрут свяжет ID вашего сокета Pusher и ID сессии Laravel. Теперь Laravel и Pusher знают, как проверить, что любое соединение сокета Pusher подключено к соответствующей сессии Laravel.

Вторая часть аутентификации и авторизации Echo — когда вы обращаетесь к защищённому ресурсу (частный канал или канал присутствия), Echo выполнит «ping» по маршруту /broadcasting/auth для проверки наличия у вас доступа к этому каналу. Поскольку ID вашего сокета связано с вашей сессией Laravel, мы можем написать простые и понятные ACL-правила для этого маршрута. Давайте приступим.

Сначала откройте config/app.php и найдите PHPApp\Providers\BroadcastServiceProvider::class, и раскомментируйте это. Теперь откройте этот файл (app/Providers/BroadcastServiceProvider.php). Там должно быть что-то такое:

PHP
...
class 
BroadcastServiceProvider extends ServiceProvider
{
    public function 
boot()
    {
        
Broadcast::routes();

        
/*
         * Аутентификация частного канала пользователя...
         */
        
Broadcast::channel('App.User.*', function ($user$userId) {
            return (int) 
$user->id === (int) $userId;
        });
    }

Здесь два важных момента. Первый — PHPBroadcast::routes() регистрирует маршруты вещания, которые Echo использует для аутентификации и авторизации.

Второй — вызов PHPBroadcast::channel() даёт вам возможность задать права доступа к каналу или группе каналов (для указания нескольких каналов используется символ PHP*). В Laravel канал по умолчанию связан с конкретным пользователем, чтобы показать, как выглядит ограничение доступа для одного, авторизованного в данный момент пользователя.

Создание прав доступа для нашего частного канала

Итак, у нас есть частный канал PHPchat-room.1. Предполагается, что у нас будет несколько комнат чата (PHPchat-room.2, и т.д.), поэтому давайте зададим права для всех комнат чата:

PHP
Broadcast::channel('chat-room.*', function ($user$chatroomId) {
    
// вернуть, авторизован ли текущий пользователь на вход в эту комнату чата
});

Как видите, первое значение, передаваемое в замыкание, — текущий пользователь, и если есть символы для подстановки вместо символа PHP*, то они будут переданы в качестве дополнительных параметров.

В рамках этой статьи мы просто сделаем заглушку авторизации, чтобы не создавать модель и миграцию для комнат чата, не добавлять связь «многие ко многим» с пользователем, и не проверять в этом замыкании, подключен ли текущий пользователь к этой комнате чата (что-то такое: PHPif ($user->chatrooms->contains($chatroomId))). Сейчас давайте просто сделаем так:

PHP
Broadcast::channel('chat-room.*', function ($user$chatroomId) {
    if (
true) { // Заменить настоящими правилами ACL
        
return true;
    }
});

Давайте проверим и посмотрим, что получилось.

Есть проблемы? Не забудьте, вам надо настроить ваш app.js на использование PHPecho.private() вместо PHPecho.channel(); вам надо изменить ваше событие для вещания в частный канал PHPchat-room-1 вместо общедоступного канала; и вам надо отредактировать PHPBroadcastServiceProvider. И ещё вам надо войти в ваше приложение. И вам надо перезапустить shgulp, если вы не используете shgulp watch.

Вы должны увидеть пустую консоль, затем вы можете вызвать нашу Artisan-команду, и увидите своего пользователя и сообщение — как и раньше, но теперь доступ есть только у аутентифицированных и авторизованных пользователей!

Если вместо этого вы видите следующее сообщение, это хорошо! Это значит, что всё работает, и ваша система решила, что вы не авторизованы на этом канале. Перепроверьте весь свой код, но помните, что всё работает — просто вы не авторизованы.

/packages/proger/habravel/uploads/655-echo-403.png

Убедитесь, что вошли в систему, и попробуйте снова.

Подписка на каналы присутствия с помощью Echo

Итак, теперь мы можем решать в «бэкенде», у каких пользователей есть доступ к определённым комнатам чата. Когда пользователь посылает сообщение в комнату чата (обычно посылая AJAX-запрос на сервер, но в нашем примере — с помощью Artisan-команды), произойдёт событие PHPChatMessageWasReceived, которое будет разослано отдельно каждому пользователю через WebSockets. Что дальше?

Предположим, мы хотим настроить индикатор для комнаты чата, показывающий, кто в ней; и может быть, мы хотим подавать звуковой сигнал при входе и выходе пользователей. Для этого есть инструмент, и он называется канал присутствия.

Для этого нам потребуется две вещи: определение разрешений нового PHPBroadcast::channel() и новый канал с префиксом PHPpresence- (который мы создадим, вернув экземпляр PHPPresenceChannel из метода PHPbroadcastOn события). Это интересно, потому что определения авторизации каналов не требуют префиксов PHPprivate- и PHPpresence-, ссылаться на PHPprivate-chat-room.1 и на PHPpresence-chat-room.1 в вызовах PHPBroadcast::channel() будем одинаково: PHPchat-room.*. Это на самом деле удобно, пока вы используете для них одинаковые правила авторизации. Но это может привести к путанице, поэтому сейчас назовём каналы немного по-другому. Давайте используем PHPpresence-chat-room-presence.1, который будем авторизовывать как PHPchat-room-presence.1.

Итак, поскольку мы говорим только о присутствии, нам не надо привязывать этот канал к событию. Вместо этого мы просто дадим PHPapp.js указание, подключить нас к каналу:

PHP
Echo.join('chat-room-presence.1')
    .
here(function (members) {
        
// запускается, когда вы заходите, и когда кто-либо другой заходит или выходит
        
console.table(members);
    });

Мы «заходим» на канал присутствия, а затем добавляем обратный вызов, который выполнится один раз при загрузке этой страницы, а затем будет выполняться каждый раз, когда другой участник заходит или выходит из этого канала присутствия. Вдобавок к PHPhere, который вызывается по всем трём событиям, вы можете добавить слушателя для PHPthen (вызывается при входе пользователя), PHPjoining (вызывается при входе на канал других пользователей), и PHPleaving (вызывается при выходе с канала других пользователей).

PHP
Echo.join('chat-room-presence.1')
    .
then(function (members) {
        
// запускается, когда вы заходите
        
console.table(members);
    })
    .
joining(function (joiningMembermembers) {
        
// запускается, когда входит другой пользователь
        
console.table(joiningMember);
    })
    .
leaving(function (leavingMembermembers) {
        
// запускается, когда выходит другой пользователь
        
console.table(leavingMember);
    });

Теперь давайте настроим авторизационные права для этого канала в PHPBroadcastServiceProvider:

PHP
Broadcast::channel('chat-room-presence.*', function ($user$roomId) {
    if (
true) { // Заменить настоящей авторизацией
        
return [
            
'id' => $user->id,
            
'name' => $user->name
        
];
    }
});

Как видите, канал присутствия не просто возвращает PHPtrue, если пользователь аутентифицирован; он должен вернуть массив данных пользователя, которые вы хотите сделать доступными, например, для использования в боковой панели «пользователи онлайн».

Вы можете удивиться тому, что можно использовать одинаковое определение PHPBroadcast::channel() и для частных каналов, и для каналов присутствия с похожими именами (PHPprivate-chat-room.* и PHPpresence-chat-room.*), потому что замыкания частных каналов должны возвращать логический тип, а замыкания каналов присутствия — массив. Однако, возврат массива тоже «логичен», и воспринимается как «да», этот пользователь имеет доступ к каналу.

Если всё соединилось правильно, то вы сможете открыть это приложение в двух разных браузерах и увидеть обновляемый список участников, выводимый в консоль при каждом входе или выходе другого пользователя:

/packages/proger/habravel/uploads/655-echo-members-in-and-out-table.png

Теперь вы можете представить, как звонить в колокольчик каждый раз при входе и выходе пользователя, вы можете обновлять свой список участников «в памяти» JavaScript и привязать это к списку «пользователи онлайн» на странице, и многое другое.

Исключение текущего пользователя

Есть ещё одна вещь, которую делает Echo: что делать, если не надо отправлять уведомления текущему пользователю? Возможно, вы хотите показывать временное всплывающее сообщение вверху экрана, когда в чат приходит новое сообщение. Но вы же не хотите показывать его пользователю, который его отправил, верно?

Чтобы исключить текущего пользователя из получателей сообщения, используйте для вызова события вместо метода PHPevent() вспомогательный метод PHPbroadcast() вместе с вызовом PHPtoOthers():

PHP
broadcast(new \App\Events\ChatMessageWasReceived($message$user))->toOthers();

Разумеется, в нашем примере с Artisan-командой этот вызов ничего не сделает, но он сработает, если событие будет вызвано пользователем вашего приложения в активной сессии.

Вот и всё!

Всё это выглядит довольно просто, поэтому я хочу рассказать, в чём прелесть.

Во-первых, обратите внимание, что сообщения, посылаемые пользователям, — это не просто текст, это JSON-представления ваших моделей. Чтобы понять, насколько это здорово, взгляните на то, как Тэйлор сделал менеджер задач, поддерживающий задачи в актуальном состоянии, на одной странице, в реальном времени в его Laracasts видео. Мощная вещь!

Во-вторых, важно понимать, что всё самое полезное Echo делает совершенно незаметно. Вы можете согласиться, что это мощная вещь, и что она открывает множество возможностей, но при этом вы можете сказать: «Но Echo же ничего не делает!»

Однако, вы не видите, как много требуется работы для настройки аутентификации, авторизации на каналах, обратных вызовов присутствия, и всего остального, что делает для вас Echo. Некоторые из этих функций есть в Pusher JS и в Socket.io с разными уровнями сложности, но в Echo они проще и согласованнее. А некоторых функций вообще нет в других библиотеках, по крайней мере в виде отдельной, простой функции. Echo берёт то, что может быть медленным и проблематичным в других сокет-библиотеках, и делает это лёгким и простым.

Как вы считаете, полезен ли этот материал? Да Нет

Комментарии (1)

dan1keen

Круто

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.