Поддержите проект сделав пожертвование.
Поделитесь своим кодом и идеями!
Поделитесь своим кодом и идеями!

Трансляция (broadcast) событий

Вы просматриваете документ для прошлой версии.
Рассмотрите возможность обновления вашего проекта до актуальной версии 11.x. Почему это важно?

Введение

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

Например, представьте, что ваше приложение может экспортировать данные пользователя в файл CSV и отправлять этот файл ему по электронной почте. Однако создание этого CSV-файла занимает несколько минут, поэтому вы можете создать и отправить CSV-файл по почте, поместив задание в очередь. Когда файл CSV будет создан и отправлен пользователю, тогда мы можем использовать широковещание для отправки события App\Events\UserDataExported, которое будет получено в JavaScript нашего приложения. Как только событие будет получено, мы можем отобразить сообщение пользователю о том, что его файл CSV был отправлен ему по электронной почте без необходимости в обновлении страницы.

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

Основные концепции широковещательной передачи просты: клиенты подключаются к именованным каналам во внешнем интерфейсе, в то время как ваше приложение Laravel транслирует события на эти каналы во внутреннем интерфейсе. Эти события могут содержать любые дополнительные данные, которые вы хотите сделать доступными для внешнего интерфейса.

Поддерживаемые драйверы

По умолчанию Laravel содержит два серверных драйвера трансляции на выбор: Pusher Channels и Ably. Однако пакеты сообщества, например, laravel-websockets и soketi, предлагают дополнительные драйверы трансляции без использования платных провайдеров.

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

Установка на стороне сервера

Чтобы начать использовать трансляцию событий Laravel, нам нужно выполнить некоторую настройку в приложении Laravel, а также установить некоторые пакеты.

Трансляция событий осуществляется серверным драйвером трансляции, который транслирует ваши события Laravel, получаемые браузером клиента через Laravel Echo (библиотека JavaScript). Не волнуйтесь – мы рассмотрим каждую часть процесса установки шаг за шагом.

Конфигурирование

Вся конфигурация трансляций событий вашего приложения хранится в конфигурационном файле config/broadcasting.php. Laravel из коробки поддерживает несколько драйверов трансляции: Pusher Channels, Redis, и драйвер log для локальной разработки и отладки. Кроме того, поддерживается драйвер null, который позволяет полностью отключить трансляцию во время тестирования. В конфигурационном файле config/broadcasting.php содержится пример конфигурации для каждого из этих драйверов.

Поставщик службы трансляции

Перед трансляцией каких-либо событий, вам сначала необходимо зарегистрировать поставщика App\Providers\BroadcastServiceProvider. В новых приложениях Laravel вам нужно только раскомментировать этого поставщика в массиве providers конфигурационного файла config/app.php. Поставщик BroadcastServiceProvider содержит код, необходимый для регистрации маршрутов авторизации трансляции.

Конфигурирование очереди

Вам также потребуется настроить и запустить обработчик очереди. Все трансляции событий выполняются через задания в очереди, так что время отклика вашего приложения не сильно зависит от транслируемых событий.

Pusher Channels

Если вы планируете транслировать свои события с помощью Pusher Channels, то вам следует установить PHP SDK Pusher Channels с помощью менеджера пакетов Composer:

composer require pusher/pusher-php-server

Далее, вы должны настроить свои учетные данные Pusher Channels в конфигурационном файле config/broadcasting.php. Пример конфигурации Pusher Channels уже содержится в этом файле, что позволяет быстро указать параметры key, secret, и app_id. Как правило, эти значения должны быть установлены через переменные окружения PUSHER_APP_KEY, PUSHER_APP_SECRET и PUSHER_APP_ID:

PUSHER_APP_ID=your-pusher-app-id
PUSHER_APP_KEY=your-pusher-key
PUSHER_APP_SECRET=your-pusher-secret
PUSHER_APP_CLUSTER=mt1

Конфигурация pusher в файле config/broadcasting.php также позволяет вам указывать дополнительные параметры, которые поддерживаются Pusher, например, cluster.

Далее, вам нужно будет изменить драйвер трансляции на pusher в файле .env:

BROADCAST_DRIVER=pusher

И, наконец, вы готовы установить и настроить Laravel Echo, который будет получать транслируемые события на клиентской стороне.

Альтернативы Pusher с открытым кодом

Пакеты laravel-websockets и soketi предоставляют совместимые с Pusher серверы WebSocket для Laravel. Эти пакеты позволяют вам использовать всю мощь вещания Laravel без использования коммерческого провайдера WebSocket. Для получения дополнительной информации об установке и использовании этих пакетов обратитесь к нашей документации по альтернативам с открытым исходным кодом.

Ably

Если вы планируете транслировать свои события с помощью Ably, то вам следует установить PHP SDK Ably с помощью менеджера пакетов Composer:

composer require ably/ably-php

Далее, вы должны настроить свои учетные данные Ably в конфигурационном файле config/broadcasting.php. Пример конфигурации Ably уже содержится в этом файле, что позволяет быстро указать параметр key. Как правило, это значение должно быть установлено через переменную окружения ABLY_KEY:

ABLY_KEY=your-ably-key

Далее, вам нужно будет изменить драйвер трансляции на ably в файле .env:

BROADCAST_DRIVER=ably

И, наконец, вы готовы установить и настроить Laravel Echo, который будет получать транслируемые события на клиентской стороне.

Альтернативы с открытым исходным кодом

PHP

Пакет laravel-websockets – это пакет для Laravel на «чистом» PHP, совместимый с Pusher. Этот пакет позволяет вам использовать всю мощь трансляции Laravel без использования платных провайдеров WebSocket. Для получения дополнительной информации об установке и использовании этого пакета обратитесь к его официальной документации.

Node

Soketi — это основанный на Node, совместимый с Pusher сервер WebSocket для Laravel. Под капотом Soketi используется µWebSockets.js для максимальной масштабируемости и скорости. Этот пакет позволяет вам использовать всю мощь вещания Laravel без коммерческого провайдера WebSocket. Для получения дополнительной информации об установке и использовании этого пакета обратитесь к его официальной документации.

Установка на стороне клиента

Pusher Channels

Laravel Echo – это JavaScript-библиотека, которая упрощает подписку на каналы и прослушивание событий, транслируемые вашим драйвером трансляции на стороне сервера. Вы можете установить Echo через менеджер пакетов NPM. В этом примере мы также установим пакет pusher-js, так как мы будем использовать вещатель Pusher Channels:

npm install --save-dev laravel-echo pusher-js

После установки Echo вы готовы создать новый экземпляр Echo в JavaScript вашего приложения. Отличное место для этого – окончание файла resources/js/bootstrap.js, который уже содержится в фреймворке Laravel. По умолчанию пример конфигурации Echo также находится в этом файле – вам просто нужно раскомментировать его:

import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});

После того как вы раскомментировали и настроили конфигурацию Echo в соответствии с вашими потребностями, вы можете скомпилировать исходники вашего приложения:

npm run dev

Чтобы узнать больше о компиляции JavaScript-исходников вашего приложения, обратитесь к документации Laravel Mix.

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

Если у вас уже есть предварительно настроенный экземпляр клиента Pusher Channels, который вы бы хотели использовать в Echo, то вы можете передать его Echo с помощью свойства конфигурации client:

import Echo from 'laravel-echo';

const client = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-channels-key',
    client: client
});

Ably

Laravel Echo – это JavaScript-библиотека, которая позволяет безболезненно подписаться на каналы и прослушивать события, транслируемые вашим драйвером трансляции на стороне сервера. Вы можете установить Echo через менеджер пакетов NPM. В этом примере мы также установим пакет pusher-js.

Вы можете задаться вопросом, зачем нам устанавливать библиотеку pusher-js JavaScript, даже если мы используем Ably для трансляции наших событий. К счастью, Ably имеет режим совместимости Pusher, который позволяет нам использовать протокол Pusher при прослушивании событий в нашем клиентском приложении:

npm install --save-dev laravel-echo pusher-js

Прежде чем продолжить, вы должны включить поддержку протокола Pusher в настройках вашего приложения Ably. Вы можете включить эту функцию в разделе настроек «Protocol Adapter Settings» панели вашего приложения Ably.

После установки Echo вы готовы создать новый экземпляр Echo в JavaScript вашего приложения. Отличное место для этого – окончание файла resources/js/bootstrap.js, который уже содержится в фреймворке Laravel. По умолчанию пример конфигурации Echo также находится в этом файле – вам просто нужно раскомментировать его; однако конфигурация по умолчанию в файле bootstrap.js предназначена для Pusher. Вы можете скопировать конфигурацию ниже, чтобы применить ее к Ably:

import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_ABLY_PUBLIC_KEY,
    wsHost: 'realtime-pusher.ably.io',
    wsPort: 443,
    disableStats: true,
    encrypted: true,
});

Обратите внимание, что наша конфигурация Echo для Ably ссылается на переменную окружения MIX_ABLY_PUBLIC_KEY. Значение этой переменной должно быть вашим публичным ключом Ably. Ваш публичный ключ – это часть ключа Ably перед символом :.

После того как вы раскомментировали и настроили конфигурацию Echo в соответствии с вашими потребностями, вы можете скомпилировать исходники вашего приложения:

npm run dev

Чтобы узнать больше о компиляции JavaScript-исходников вашего приложения, обратитесь к документации Laravel Mix.

Обзор концепции

Трансляция событий Laravel позволяет транслировать серверные события Laravel в JavaScript-приложение на клиентской стороне, используя драйверный подход к WebSockets. В настоящее время Laravel поставляется с драйверами Pusher Channels и Ably. События могут быть легко обработаны на стороне клиента с помощью JavaScript-пакета Laravel Echo.

События транслируются по «каналам», которые могут быть публичными или частными. Любой посетитель вашего приложения может подписаться на публичный канал без какой-либо аутентификации или авторизации; однако, чтобы подписаться на частный канал, пользователь должен быть аутентифицирован и авторизован для прослушивания событий на этом канале.

{tip} Если вы хотите изучить альтернативы Pusher с открытым исходным кодом, ознакомьтесь с альтернативами с открытым исходным кодом.

Пример использования

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

Предположим, что в нашем приложении у нас есть страница, которая позволяет пользователям просматривать статус доставки своих заказов. Предположим также, что событие OrderShipmentStatusUpdated запускается, когда приложение обрабатывает обновление статуса доставки:

use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);

Интерфейс ShouldBroadcast

Когда пользователь просматривает один из своих заказов, мы не хотим, чтобы ему приходилось обновлять страницу для просмотра статуса обновлений. Вместо этого мы хотим транслировать обновления в приложение по мере их создания. Итак, нам нужно пометить событие OrderShipmentStatusUpdated интерфейсом ShouldBroadcast. Это проинструктирует Laravel транслировать событие при его запуске:

<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    /**
     * Экземпляр заказа.
     *
     * @var \App\Order
     */
    public $order;
}

Интерфейс ShouldBroadcast требует, чтобы в нашем классе события был определен метод broadcastOn. Этот метод отвечает за возврат каналов, по которым должно транслироваться событие. Пустая заглушка этого метода уже определена в сгенерированных классах событий, поэтому нам нужно только заполнить ее реализацию. Мы хотим, чтобы только создатель заказа мог просматривать статус обновления, поэтому мы будем транслировать событие на частном канале, привязанном к конкретному заказу:

/**
 * Получить каналы трансляции события.
 *
 * @return \Illuminate\Broadcasting\PrivateChannel
 */
public function broadcastOn()
{
    return new PrivateChannel('orders.'.$this->order->id);
}

Авторизация каналов

Помните, что пользователи должны иметь разрешение на прослушивание частных каналов. Мы можем определить наши правила авторизации каналов в файле routes/channels.php нашего приложения. В этом примере нам нужно убедиться, что любой пользователь, пытающийся прослушивать частный канал orders.1, на самом деле является создателем заказа:

use App\Models\Order;

Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

Метод channel принимает два аргумента: имя канала и замыкание, которое возвращает true или false, указывая тем самым, имеет ли пользователь право прослушивать канал.

Все замыкания авторизации получают текущего аутентифицированного пользователя в качестве своего первого аргумента и любые дополнительные параметры в качестве своих последующих аргументов. В этом примере мы используем заполнитель {orderId}, чтобы указать, что часть «ID» имени канала является параметром.

Прослушивание трансляций событий

Далее все, что остается, – это прослушивать событие в нашем JavaScript-приложении. Мы можем сделать это с помощью Laravel Echo. Во-первых, мы будем использовать метод private для подписки на частный канал. Затем мы можем использовать метод listen для прослушивания события OrderShipmentStatusUpdated. По умолчанию все публичные свойства события будут включены в трансляцию события:

Echo.private(`orders.${orderId}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order);
    });

Определение транслируемых событий

Чтобы сообщить Laravel, что какое-то событие должно транслироваться, вы должны реализовать интерфейс Illuminate\Contracts\Broadcasting\ShouldBroadcast в классе события. Этот интерфейс уже импортирован во все классы событий, сгенерированные фреймворком, поэтому вы с легкостью можете добавить его к любому из ваших событий.

Интерфейс ShouldBroadcast требует, чтобы вы реализовали единственный метод: broadcastOn. Метод broadcastOn должен возвращать канал или массив каналов, по которым должно транслироваться событие. Каналы должны быть экземплярами Channel, PrivateChannel или PresenceChannel. Экземпляры Channel представляют собой публичные каналы, на которые может подписаться любой пользователь, в то время как PrivateChannels и PresenceChannels представляют собой частные каналы, для которых требуется авторизация канала:

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    /**
     * Пользователь, создавший сервер.
     *
     * @var \App\Models\User
     */
    public $user;

    /**
     * Создать новый экземпляр события.
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Получить каналы трансляции события.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('user.'.$this->user->id);
    }
}

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

Имя транслируемого события

По умолчанию Laravel будет транслировать событие, используя имя класса события. Однако вы можете изменить имя транслируемого события, определив для события метод broadcastAs:

/**
 * Имя транслируемого события.
 *
 * @return string
 */
public function broadcastAs()
{
    return 'server.created';
}

Если вы измените имя транслируемого события с помощью метода broadcastAs, то вы должны убедиться, что зарегистрировали ваш слушатель с ведущим символом .. Это проинструктирует Echo не добавлять пространство имен приложения к событию:

.listen('.server.created', function (e) {
    ....
});

Данные трансляции

При трансляции события, все его публичные свойства автоматически сериализуются и транслируются как полезная нагрузка события, что позволяет вам получить доступ к любым его публичным данным из вашего JavaScript-приложения. Так, например, если ваше событие имеет единственное публичное свойство $user, представляющее собой модель Eloquent, то полезная нагрузка при трансляции события будет:

{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}

Однако, если вы хотите иметь более точный контроль над полезной нагрузкой трансляции, то вы можете определить метод broadcastWith вашего события. Этот метод должен возвращать массив данных, которые вы хотите использовать в качестве полезной нагрузки при трансляции события:

/**
 * Получите данные для трансляции.
 *
 * @return array
 */
public function broadcastWith()
{
    return ['id' => $this->user->id];
}

Очередь трансляции

По умолчанию каждое транслируемое событие помещается в очередь по умолчанию и соединение очереди по умолчанию, указанные в вашем конфигурационном файле config/queue.php. Вы можете изменить соединение очереди и имя, используемое вещателем, определив свойства connection и queue в вашем классе события:

/**
 * Имя соединения очереди, которое будет использоваться при трансляции события.
 *
 * @var string
 */
public $connection = 'redis';

/**
 * Имя очереди, в которую нужно поместить задание трансляции.
 *
 * @var string
 */
public $queue = 'default';

В качестве альтернативы вы можете настроить имя очереди, определив в методе broadcastQueue вашего события:

/**
 * The name of the queue on which to place the broadcasting job.
 *
 * @return string
 */
public function broadcastQueue()
{
    return 'default';
}

Если вы хотите транслировать свое событие с помощью очереди sync вместо драйвера очереди по умолчанию, то вы можете реализовать интерфейс ShouldBroadcastNow вместо ShouldBroadcast:

<?php

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
    //
}

Условия трансляции

Иногда необходимо транслировать событие только в том случае, если выполняется определенное условие. Вы можете определить эти условия, добавив метод broadcastWhen в ваш класс события:

/**
 * Определить, условия трансляции события.
 *
 * @return bool
 */
public function broadcastWhen()
{
    return $this->order->value > 100;
}

Трансляция и транзакции базы данных

Когда транслируемые события отправляются в транзакциях базы данных, они могут быть обработаны очередью до того, как транзакция базы данных будет зафиксирована. Когда это происходит, любые обновления, внесенные вами в модели или записи базы данных во время транзакции базы данных, могут еще не быть отражены в базе данных. Кроме того, любые модели или записи базы данных, созданные в рамках транзакции, могут не существовать в базе данных. Если ваше событие зависит от этих моделей, могут возникнуть непредвиденные ошибки при обработке задания, транслирующего событие.

Если для параметра after_commit конфигурации вашего соединения с очередью установлено значение false, то вы все равно можете указать, что конкретное транслируемое событие должно быть отправлено после того, как все открытые транзакции базы данных были зафиксированы, путем определения свойства $afterCommit в классе события:

<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    public $afterCommit = true;
}

Чтобы узнать больше о том, как обойти эти проблемы, просмотрите документацию, касающуюся заданий в очереди и транзакций базы данных.

Авторизация каналов

Частные каналы требуют, чтобы текущий аутентифицированный пользователь был авторизован и действительно мог прослушивать канал. Это достигается путем отправки HTTP-запроса вашему приложению Laravel с именем канала, что позволит вашему приложению определить, может ли пользователь прослушивать этот канал. При использовании Laravel Echo HTTP-запрос на авторизацию подписок на частные каналы будет выполнен автоматически; однако вам необходимо определить верные маршруты для ответа на эти запросы.

Определение маршрутов авторизации

Laravel упрощает определение маршрутов для ответа на запросы об авторизации канала. В поставщике App\Providers\BroadcastServiceProvider вашего приложения, вы увидите вызов метода Broadcast::routes. Этот метод зарегистрирует маршрут /broadcasting/auth для обработки запросов авторизации:

Broadcast::routes();

Метод Broadcast::routes автоматически применит посредника web для группы своих маршрутов; однако вы можете передать в метод массив атрибутов маршрута, если хотите изменить присваиваемые им атрибуты:

Broadcast::routes($attributes);

Изменение конечной точки авторизации

По умолчанию Echo будет использовать конечную точку /broadcasting/auth для авторизации доступа к каналу. Однако вы можете указать свою собственную конечную точку авторизации, передав параметр конфигурации authEndpoint вашему экземпляру Echo:

window.Echo = new Echo({
    broadcaster: 'pusher',
    // ...
    authEndpoint: '/custom/endpoint/auth'
});

Настройка запроса на авторизацию

Вы можете настроить, как Laravel Echo выполняет запросы авторизации, предоставив настраиваемый авторизатор при инициализации Echo:

window.Echo = new Echo({
    // ...
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                axios.post('/api/broadcasting/auth', {
                    socket_id: socketId,
                    channel_name: channel.name
                })
                .then(response => {
                    callback(false, response.data);
                })
                .catch(error => {
                    callback(true, error);
                });
            }
        };
    },
})

Определение авторизации канала

Затем нам нужно определить логику, которая фактически будет определять, может ли текущий аутентифицированный пользователь прослушивать указанный канал. Это делается в файле routes/channels.php, находящемся в вашем приложении. В этом файле вы можете использовать метод Broadcast::channel для регистрации замыканий авторизации канала:

Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

Метод channel принимает два аргумента: имя канала и замыкание, которое возвращает true или false, указывая тем самым, имеет ли пользователь право прослушивать канал.

Все замыкания авторизации получают текущего аутентифицированного пользователя в качестве своего первого аргумента и любые дополнительные параметры в качестве своих последующих аргументов. В этом примере мы используем заполнитель {orderId}, чтобы указать, что часть «ID» имени канала является параметром.

Привязка модели к авторизации

Как и HTTP-маршруты, для маршрутов каналов также могут использоваться неявные и явные привязки модели к маршруту. Например, вместо получения строкового или числового идентификатора заказа вы можете запросить фактический экземпляр модели Order:

use App\Models\Order;

Broadcast::channel('orders.{order}', function ($user, Order $order) {
    return $user->id === $order->user_id;
});

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

Предварительная аутентификация авторизации канала

Частные каналы и каналы присутствия аутентифицируют текущего пользователя через стандартного охранника аутентификации вашего приложения. Если пользователь не аутентифицирован, то авторизация канала автоматически отклоняется, и обратный вызов авторизации никогда не выполняется. Однако вы можете назначить несколько своих охранников, которые должны при необходимости аутентифицировать входящий запрос:

Broadcast::channel('channel', function () {
    // ...
}, ['guards' => ['web', 'admin']]);

Определение класса канала

Если ваше приложение использует много разных каналов, то ваш файл routes/channels.php может стать громоздким. Таким образом, вместо использования замыканий для авторизации каналов вы можете использовать классы каналов. Чтобы сгенерировать новый канал, используйте команду make:channel Artisan. Эта команда поместит новый класс канала в каталог app/Broadcasting вашего приложения:

php artisan make:channel OrderChannel

Затем зарегистрируйте свой канал в файле routes/channels.php:

use App\Broadcasting\OrderChannel;

Broadcast::channel('orders.{order}', OrderChannel::class);

Наконец, вы можете поместить логику авторизации для своего канала в метод join класса канала. Этот метод будет содержать ту же логику, которую вы обычно использовали бы в замыкании при авторизации вашего канала. Вы также можете воспользоваться преимуществами привязки модели канала:

<?php

namespace App\Broadcasting;

use App\Models\Order;
use App\Models\User;

class OrderChannel
{
    /**
     * Создать новый экземпляр канала.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Подтвердить доступ пользователя к каналу.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return array|bool
     */
    public function join(User $user, Order $order)
    {
        return $user->id === $order->user_id;
    }
}

Как и многие другие классы в Laravel, классы каналов будут автоматически разрешены контейнером служб. Таким образом, вы можете указать любые зависимости, необходимые для вашего канала, в его конструкторе.

Трансляция событий

После того как вы определили событие и отметили его интерфейсом ShouldBroadcast, вам нужно только запустить событие, используя метод отправки события. Диспетчер событий заметит, что событие помечено интерфейсом ShouldBroadcast, и поставит событие в очередь для дальнейшей трансляции:

use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);

Трансляция событий только остальным пользователям

При создании приложения, использующего трансляцию событий, иногда может потребоваться трансляция события всем подписчикам канала, кроме текущего пользователя. Вы можете сделать это с помощью помощника broadcast и метода toOthers:

use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->toOthers();

Чтобы лучше понять необходимость использования метода toOthers, давайте представим приложение со списком задач, в котором пользователь может создать новую задачу, введя имя задачи. Чтобы создать задачу, ваше приложение может сделать запрос к URL-адресу /task, который транслирует создание задачи и возвращает JSON-представление новой задачи. Когда ваше JavaScript-приложение получает ответ от конечной точки, оно может напрямую вставить новую задачу в свой список задач следующим образом:

axios.post('/task', task)
    .then((response) => {
        this.tasks.push(response.data);
    });

Однако помните, что мы также транслируем создание задачи. Если ваше JavaScript-приложение также прослушивает это событие, чтобы добавить задачи в список задач, у вас будут дублирующиеся задачи в вашем списке: одна из конечной точки и одна из трансляции. Вы можете решить эту проблему, используя метод toOthers, чтобы указать вещателю не транслировать событие текущему пользователю.

Ваше событие должно использовать трейт Illuminate\Broadcasting\InteractsWithSockets для вызова метода toOthers.

Конфигурирование при использовании метода toOthers

Когда вы инициализируете экземпляр Laravel Echo, соединению назначается идентификатор сокета. Если вы используете глобальный экземпляр Axios для выполнения HTTP-запросов из вашего JavaScript-приложения, то идентификатор сокета будет автоматически прикрепляться к каждому исходящему запросу в заголовке X-Socket-ID. Затем, когда вы вызываете метод toOthers, Laravel извлечет идентификатор сокета из заголовка и проинструктирует вещателя не транслировать никакие соединения с этим идентификатором сокета.

Если вы не используете глобальный экземпляр Axios, то вам необходимо вручную сконфигурировать JavaScript-приложение для отправки заголовка X-Socket-ID со всеми исходящими запросами. Вы можете получить идентификатор сокета, используя метод Echo.socketId:

var socketId = Echo.socketId();

Настройка подключения

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

use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');

В качестве альтернативы вы можете указать широковещательное соединение события, вызвав метод broadcastVia в конструкторе события. Однако перед этим вы должны убедиться, что класс событий использует трейт InteractsWithBroadcasting:

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    use InteractsWithBroadcasting;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->broadcastVia('pusher');
    }
}

Прием трансляций

Прослушивание событий

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

Echo.channel(`orders.${this.order.id}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order.name);
    });

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

Echo.private(`orders.${this.order.id}`)
    .listen(...)
    .listen(...)
    .listen(...);

Остановка прослушивания событий

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

Echo.private(`orders.${this.order.id}`)
    .stopListening('OrderShipmentStatusUpdated')

Покидание канала

Чтобы покинуть канал, вы можете вызвать метод leaveChannel вашего экземпляра Echo:

Echo.leaveChannel(`orders.${this.order.id}`);

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

Echo.leave(`orders.${this.order.id}`);

Пространства имён

Вы могли заметить в приведенных выше примерах, что мы не указали полное пространство имен App\Events для классов событий. Это связано с тем, что Echo автоматически предполагает, что события находятся в пространстве имен App\Events. Однако вы можете изменить корневое пространство имен при создании экземпляра Echo, передав параметр конфигурации namespace:

window.Echo = new Echo({
    broadcaster: 'pusher',
    // ...
    namespace: 'App.Other.Namespace'
});

В качестве альтернативы вы можете добавить к классам событий префикс . при подписке на них с помощью Echo. Это позволит вам всегда указывать полное имя класса:

Echo.channel('orders')
    .listen('.Namespace\\Event\\Class', (e) => {
        //
    });

Каналы присутствия

Каналы присутствия основаны на безопасности частных каналов, но в то же время предоставляя дополнительную функцию осведомленности о том, кто подписан на канал. Это упрощает создание мощных функций приложения для совместной работы, таких как уведомление пользователей, когда другой пользователь просматривает ту же страницу, или перечисление пользователей комнаты чата.

Авторизация каналов присутствия

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

Данные, возвращаемые замыканием авторизации, будут доступны для слушателей событий канала присутствия в вашем JavaScript-приложении. Если пользователь не авторизован для присоединения к каналу присутствия, то вы должны вернуть false или null:

Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});

Присоединение к каналам присутствия

Чтобы присоединиться к каналу присутствия, вы можете использовать метод join Echo. Метод join вернет реализацию PresenceChannel, которая, наряду с методом listen, позволяет вам подписаться на события here, joining и leave.

Echo.join(`chat.${roomId}`)
    .here((users) => {
        //
    })
    .joining((user) => {
        console.log(user.name);
    })
    .leaving((user) => {
        console.log(user.name);
    })
    .error((error) => {
        console.error(error);
    });

Замыкание here будет выполнено сразу после успешного присоединения к каналу и получит массив, содержащий информацию о пользователе для всех других пользователей, которые в настоящее время подписаны на канал. Метод joining будет выполняться, когда новый пользователь присоединяется к каналу, а метод leaving будет выполняться при покидании пользователем канала. Метод error будет выполнен, когда при аутентификации возвращается код HTTP-статуса отличный от 200 или если есть проблема с парсингом возвращаемого JSON.

Трансляция на каналы присутствия

Каналы присутствия могут получать события так же, как публичные или частные каналы. Используя пример чата, мы можем захотеть транслировать события NewMessage на канал присутствия комнаты. Для этого мы вернем экземпляр 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();

Как и для других типов событий, вы можете прослушивать события, отправленные в каналы присутствия, используя метод listen Echo:

Echo.join(`chat.${roomId}`)
    .here(...)
    .joining(...)
    .leaving(...)
    .listen('NewMessage', (e) => {
        //
    });

Трансляция моделей

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

Обычно транслируются события, когда модели Eloquent создаются, обновляются или удаляются. Конечно, это легко можно сделать вручную, определив пользовательские события для изменений состояния модели Eloquent и пометив эти события с помощью интерфейса ShouldBroadcast.

Однако, если вы не используете эти события для каких-либо других целей в своем приложении, может оказаться обременительным создание классов событий с единственной целью их широковещательной передачи. Чтобы исправить это, Laravel позволяет вам указать, что модель Eloquent должна автоматически транслировать изменения своего состояния.

Для начала ваша модель Eloquent должна использовать трейт Illuminate\Database\Eloquent\BroadcastsEvents. Кроме того, модель должна определять метод broadcastsOn, возвращающий массив каналов, по которым должны транслироваться события модели:

<?php

namespace App\Models;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Database\Eloquent\BroadcastsEvents;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use BroadcastsEvents, HasFactory;

    /**
     * Получите пользователя, которому принадлежит сообщение.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Получите каналы, по которым должны транслироваться события модели.
     *
     * @param  string  $event
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn($event)
    {
        return [$this, $this->user];
    }
}

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

Кроме того, вы могли заметить, что метод broadcastOn получает строковый аргумент $event. Этот аргумент содержит тип события, которое произошло в модели, и будет иметь значение created, updated, deleted, trashed или restored. Проверяя значение этой переменной, вы можете определить, на какие каналы (если есть) модель должна транслировать конкретное событие:

/**
 * Получите каналы, по которым должны транслироваться события модели.
 *
 * @param  string  $event
 * @return \Illuminate\Broadcasting\Channel|array
 */
public function broadcastOn($event)
{
    return match ($event) {
        'deleted' => [],
        default => [$this, $this->user],
    };
}

Настройка создания события трансляции модели

Иногда вы можете захотеть настроить то, как Laravel создает базовое событие трансляции модели. Вы можете добиться этого, определив метод newBroadcastableEvent в вашей модели Eloquent. Этот метод должен возвращать экземпляр Illuminate\Database\Eloquent\BroadcastableModelEventOccurred:

use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred

/**
 * Создайте новое транслируемое событие для модели.
 *
 * @param  string  $event
 * @return \Illuminate\Database\Eloquent\BroadcastableModelEventOccurred
 */
protected function newBroadcastableEvent($event)
{
    return (new BroadcastableModelEventOccurred(
        $this, $event
    ))->dontBroadcastToCurrentUser();
}

Соглашение о трансляции моделей

Соглашения о каналах

Как вы могли заметить, метод broadcastOn в приведенном выше примере модели не возвращал экземпляры Channel. Вместо этого модели Eloquent возвращались напрямую. Если экземпляр модели Eloquent возвращается методом broadcastOn вашей модели (или содержится в массиве, возвращаемом методом), Laravel автоматически создаст экземпляр частного канала для модели, используя имя класса модели и идентификатор первичного ключа в качестве названия канала.

Итак, модель App\Models\User с id равным 1 будет преобразована в экземпляр Illuminate\Broadcasting\PrivateChannel с именем App.Models.User.1. Конечно, в дополнение к возврату экземпляров модели Eloquent из метода broadcastOn вашей модели, вы можете возвращать полные экземпляры Channel чтобы иметь полный контроль над именами каналов модели:

use Illuminate\Broadcasting\PrivateChannel;

/**
 * Получите каналы, по которым должны транслироваться события модели.
 *
 * @param  string  $event
 * @return \Illuminate\Broadcasting\Channel|array
 */
public function broadcastOn($event)
{
    return [new PrivateChannel('user.'.$this->id)];
}

Если вы планируете явно возвращать экземпляр канала из метода broadcastOn вашей модели, вы можете передать экземпляр модели Eloquent в конструктор канала. При этом Laravel будет использовать описанные выше соглашения о каналах модели, чтобы преобразовать модель Eloquent в строку имени канала:

return [new Channel($this->user)];

Если вам нужно определить имя канала модели, вы можете вызвать метод broadcastChannel для любого экземпляра модели. Например, этот метод возвращает строку App.Models.User.1 для модели App\Models\User с id равным 1:

$user->broadcastChannel()

Соглашение о событиях

Поскольку события трансляции моделей не связаны с «фактическим» событием в каталоге App\Events вашего приложения, им присваиваются имя и полезная нагрузка на основе соглашений. Соглашение Laravel состоит в том, чтобы транслировать событие, используя имя класса модели (не включая пространство имен) и имя события модели, которое инициировало трансляцию.

Так, например, обновление модели App\Models\Post будет транслировать событие в ваше клиентское приложение как PostUpdated со следующей полезной нагрузкой:

{
    "model": {
        "id": 1,
        "title": "My first post"
        ...
    },
    ...
    "socket": "someSocketId",
}

Удаление модели App\Models\User приведет к трансляции события с именем UserDeleted.

При желании вы можете определить собственное имя трансляции и полезную нагрузку, добавив к вашей модели методы broadcastAs и broadcastWith. Эти методы получают имя происходящего события / операции модели, что позволяют вам настроить имя события и полезную нагрузку для каждой операции модели. Если null возвращается из метода broadcastAs, Laravel будет использовать соглашения об именах широковещательных событий модели, обсужденные выше:

/**
 * The model event's broadcast name.
 *
 * @param  string  $event
 * @return string|null
 */
public function broadcastAs($event)
{
    return match ($event) {
        'created' => 'post.created',
        default => null,
    };
}

/**
 * Get the data to broadcast for the model.
 *
 * @param  string  $event
 * @return array
 */
public function broadcastWith($event)
{
    return match ($event) {
        'created' => ['title' => $this->title],
        default => ['model' => $this],
    };
}

Прослушивание трансляций моделей

После того как вы добавили в модель трейт BroadcastsEvents и определили метод broadcastOn модели, вы готовы начать прослушивание транслируемых событий модели в своем клиентском приложении. Перед тем как начать, вы можете ознакомиться с полной документацией по прослушиванию событий.

Сначала используйте метод private для получения экземпляра канала, затем вызовите метод listen для прослушивания указанного события. Как правило, имя канала, присвоенное методу private должно соответствовать соглашению о трансляции моделей.

Как только вы получили экземпляр канала, вы можете использовать метод listen для прослушивания определенного события. Поскольку события трансляции моделей не связаны с «фактическим» событием в каталоге вашего приложения App\Events directory, имя события должно иметь префикс ., чтобы указать, что оно не принадлежит определенному пространству имен. Каждое событие трансляции модели имеет свойство model, которое содержит все транслируемые свойства модели:

Echo.private(`App.Models.User.${this.user.id}`)
    .listen('.PostUpdated', (e) => {
        console.log(e.model);
    });

Клиентские события

При использовании Pusher Channels вы должны включить опцию «Client Events» в разделе «App Settings» вашей панели управления приложения для отправки клиентских событий.

По желанию можно транслировать событие другим подключенным клиентам, вообще не затрагивая ваше приложение Laravel. Это может быть особенно полезно для таких вещей, как «ввод» уведомлений, когда вы хотите предупредить пользователей вашего приложения о том, что другой пользователь печатает сообщение.

Чтобы транслировать клиентские события, вы можете использовать метод whisper Echo:

Echo.private(`chat.${roomId}`)
    .whisper('typing', {
        name: this.user.name
    });

Чтобы прослушивать клиентские события, вы можете использовать метод listenForWhisper:

Echo.private(`chat.${roomId}`)
    .listenForWhisper('typing', (e) => {
        console.log(e.name);
    });

Уведомления

Если связать трансляцию событий с уведомлениями, то ваше JavaScript-приложение может получать новые уведомления по мере их появления без необходимости в обновлении страницы. Перед началом работы обязательно прочтите документацию по использованию канала транслируемых уведомлений.

После того как вы настроили уведомление для использования трансляции канала, вы можете прослушивать транслируемые события, используя метод notification Echo. Помните, что имя канала должно соответствовать имени класса объекта, получающего уведомления:

Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        console.log(notification.type);
    });

В этом примере все уведомления, отправленные экземплярам App\Models\User через канал broadcast, будут получены в замыкании. Авторизация канала App.Models.User.{id} уже изначально содержится в BroadcastServiceProvider фреймворка Laravel.