Введение
События в Laravel представлены реализацией паттерна Observer, что позволяет вам подписываться и прослушивать различные события, возникающие в вашем приложения. Как правило, классы событий находятся в папке app/Events, а классы обработчиков событий — в app/Listeners. Если у вас нет этих папок, не переживайте, они будут созданы при создании событий и слушателей с помощью Artisan-команд.
События — отличный способ для разделения различных аспектов вашего приложения, поскольку одно событие может иметь несколько слушателей, независящих друг от друга. Например, вы можете отправлять пользователю Slack-уведомление каждый раз, когда заказ доставлен. Вместо привязки кода обработки заказа к коду Slack-уведомления вы можете просто создать событие OrderShipped, которое сможет получить слушатель и преобразовать в Slack-уведомление.
Регистрация событий и слушателей
Сервис-провайдер EventServiceProvider, включённый в ваше Laravel приложение, предоставляет удобное место для регистрации всех слушателей событий. Свойство listen содержит массив всех событий (ключей) и их слушателей (значения). Конечно, вы можете добавить столько событий в этот массив, сколько требуется вашему приложению. Например, давайте добавим событие OrderShipped:
/**
* Слушатель события в вашем приложении.
*
* @var array
*/
protected $listen = [
'App\Events\OrderShipped' => [
'App\Listeners\SendShipmentNotification',
],
];
Генерация классов событий и слушателей
Конечно, вручную создавать файлы для каждого события и слушателя затруднительно. Вместо этого добавьте слушателей и события в ваш EventServiceProvider и используйте команду shevent:generate
. Эта команда сгенерирует все события и слушателей, которые перечислены в вашем EventServiceProvider.
Конечно, уже существующие события и слушатели останутся нетронутыми:
shphp artisan event:generate
Регистрация событий вручную
Как правило, события должны регистрироваться через массив $listen в EventServiceProvider.
добавлено в 5.3 ()
добавлено в 5.2 () 5.1 () 5.0 ()
Однако, также вы можете регистрировать события вручную с обработчиком событий, используя либо фасад Event, либо реализацию контракта Illuminate\Contracts\Events\Dispatcher
/**
* Регистрация своих событий в приложении.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('event.name', function ($foo, $bar) {
//
});
}
Вы даже можете регистрировать слушателей, используя символ * как маску, что позволит вам поймать несколько событий для одного слушателя. Такой метод вернёт весь массив данных событий одним параметром:
Event::listen('event.*', function (array $data) {
//
});
Определение событий
Класс события — это просто контейнер данных, содержащий информацию, которая относится к событию.
добавлено в 5.3 ()
Например, предположим, что наше сгенерированное событие OrderShipped принимает объект Eloquent ORM:
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use SerializesModels;
public $order;
/**
* Создание нового экземпляра события.
*
* @param Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
Как видите, этот класс события не содержит никакой логики. Это просто контейнер для объекта Order. Типаж SerializesModels, используемый событием, корректно сериализирует любые Eloquent модели, если объект события будет сериализирован php-функцией PHPserialize()
.
добавлено в 5.2 () 5.1 () 5.0 ()
Например, предположим, что наше сгенерированное событие PodcastWasPurchased принимает объект Eloquent ORM:
<?php
namespace App\Events;
use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class PodcastWasPurchased extends Event
{
use SerializesModels;
public $podcast;
/**
* Создание нового экземпляра события.
*
* @param Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
}
Как видите, этот класс события не содержит никакой логики. Это просто контейнер для объекта Podcast. Типаж SerializesModels, используемый событием, корректно сериализирует любые Eloquent модели, если объект события будет сериализирован php-функцией PHPserialize()
.
Определение слушателей
Теперь давайте взглянем на слушателя для нашего примера события. Слушатели событий принимают экземпляр события в свой метод PHPhandle()
. Команда shevent:generate
автоматически импортирует класс события и указывает тип события в метод PHPhandle()
. В методе PHPhandle()
вы можете выполнять любые действия, необходимые для ответа на событие.
добавлено в 5.3 ()
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* Создание слушателя события.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Обработка события.
*
* @param OrderShipped $event
* @return void
*/
public function handle(OrderShipped $event)
{
// Доступ к order, используя $event->order...
}
}
Ваши слушатели события могут также указывать тип любых зависимостей, которые необходимы для их конструкторов. Все слушатели события доступны через сервис-контейнер Laravel, поэтому зависимости будут инъецированы автоматически.
добавлено в 5.2 () 5.1 () 5.0 ()
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
// для версии 5.1 и ранее:
// use Illuminate\Queue\InteractsWithQueue;
// use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation
{
/**
* Создание слушателя события.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Обработка события.
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// Доступ к podcast, используя $event->podcast...
}
}
Ваши слушатели события могут также указывать тип любых зависимостей, которые необходимы для их конструкторов. Все слушатели события доступны через сервис-контейнер Laravel, поэтому зависимости будут инъецированы автоматически:
use Illuminate\Contracts\Mail\Mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
Остановка распространения события
Иногда, вам необходимо остановить распространение события для других слушателей. Вы можете сделать это, возвратив false из метода PHPhandle()
вашего слушателя.
Слушатели события в очереди
Поместить слушателя в очередь может быть полезно, если ваш слушатель будет выполнять медленную задачу, например, отправку e-mail или выполнение HTTP-запроса. Прежде чем помещать слушателей в очередь, не забудьте настроить вашу очередь и запустить слушателя очереди на вашем сервере или в локальной среде разработки.
Чтобы указать, что слушатель должен быть поставлен в очередь, добавьте интерфейс ShouldQueue в класс слушателя. В слушателях, сгенерированных Artisan-командой shevent:generate
, уже импортирован этот интерфейс в текущее пространство имен. Так что вы можете сразу использовать его:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
// для версии 5.1 и ранее:
// use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
//
}
Вот и всё! Теперь, когда этого слушателя вызывают для события, он будет автоматически поставлен в очередь диспетчером события, использующим систему очереди Laravel. Если никакие исключения не будут выброшены, когда слушатель выполняется из очереди, то задача в очереди будет автоматически удалена после завершения её выполнения.
Ручной доступ к очереди
Если вам необходимо вручную получить доступ к базовым методам очереди слушателя PHPdelete()
и PHPrelease()
, вы можете сделать это с помощью типажа Illuminate\Queue\InteractsWithQueue. Этот типаж по умолчанию импортирован в сгенерированные слушатели и предоставляет доступ к этим методам:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
public function handle(OrderShipped $event)
{
if (true) {
$this->release(30);
}
}
}
Запуск событий
добавлено в 5.3 ()
Чтобы запустить событие, вы можете передать экземпляр события во вспомогательный метод PHPevent()
. Этот метод распространит событие для всех его зарегистрированных слушателей. Поскольку метод PHPevent()
доступен глобально, вы можете вызвать его из любого места вашего приложения:
<?php
namespace App\Http\Controllers;
use App\Order;
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
class OrderController extends Controller
{
/**
* Доставка данного заказа.
*
* @param int $orderId
* @return Response
*/
public function ship($orderId)
{
$order = Order::findOrFail($orderId);
// Логика доставки заказа...
event(new OrderShipped($order));
}
}
При тестировании может быть полезным проверка запуска некоторых событий без уведомления их слушателей. в этом вам помогут встроенные вспомогательные функции для тестирования Laravel.
добавлено в 5.2 () 5.1 () 5.0 ()
Чтобы запустить событие, вы можете использовать фасад Event, передав экземпляр события методу PHPfire()
. Метод PHPfire()
распространит событие для всех его зарегистрированных слушателей:
<?php
namespace App\Http\Controllers;
use Event;
use App\Podcast;
use App\Events\PodcastWasPurchased;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Показать профиль заданного пользователя.
*
* @param int $userId
* @param int $podcastId
* @return Response
*/
public function purchasePodcast($userId, $podcastId)
{
$podcast = Podcast::findOrFail($podcastId);
// Логика покупки podcast...
Event::fire(new PodcastWasPurchased($podcast));
}
}
Также вы можете использовать глобальную вспомогательную функцию PHPevent()
для запуска события:
event(new PodcastWasPurchased($podcast));
Широковещательные события
Во многих современных веб-приложениях используются веб-сокеты, чтобы реализовать быстро обновляющиеся пользовательские интерфейсы реального времени. Когда некоторые данные обновлены на сервере, сообщение обычно отправляется по websocket соединению, которое будет обработано клиентом.
Чтобы помочь вам в создании этих типов приложений, Laravel упрощает «передачу» ваших событий по websocket соединению. Широковещательные события Laravel позволяют вам совместно использовать те же имена событий между серверным кодом и клиентской платформой JavaScript.
Конфигурация
Все параметры широковещательных событий находятся в конфигурационном файле config/broadcasting.php. Laravel поддерживает несколько широковещательных драйверов из коробки: Pusher, Redis и драйвер log для локальной разработки и отладки. Пример конфигурации включен для каждого из этих драйверов.
Требования к широковещательным событиям
Следующие зависимости будут необходимы:
- Pusher: pusher/pusher-php-server ~2.0
- Redis: predis/predis ~1.0
Перед использованием широковещательных событий также вам будет необходимо сконфигурировать и запустить слушателя очереди. Все широковещательные события используют очереди, чтобы не уменьшать время отклика вашего приложения.
Помечаем широковещательные события
Чтобы проинформировать Laravel о том, что заданное событие должно быть широковещательным, реализуйте интерфейс Illuminate\Contracts\Broadcasting\ShouldBroadcast в классе события. Интерфейс ShouldBroadcast требует реализации одного метода: PHPbroadcastOn()
. Метод PHPbroadcastOn()
должен возвращать массив «channel» имён, для которого событие должно быть широковещательным:
<?php
namespace App\Events;
use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ServerCreated extends Event implements ShouldBroadcast
{
use SerializesModels;
public $user;
/**
* Создание нового экземпляра события.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Получение каналов, для которых событие должно быть широковещательным.
*
* @return array
*/
public function broadcastOn()
{
return ['user.'.$this->user->id];
}
}
Теперь вам нужно только запустить событие, как вы это обычно делали. Как только событие было запущено, обработчик очереди автоматически передаст широковещательное событие по вашему указанному широковещательному драйверу.
Переписываем имена широковещательных событий
По умолчанию широковещательное имя события будет полностью определенным именем класса события. Используя класс в качестве примера выше, широковещательное событие было бы App\Events\ServerCreated. Вы можете определить имя широковещательного события, как вам будет удобнее, используя метод PHPbroadcastAs()
:
/**
* Получить имя широковещательного события.
*
* @return string
*/
public function broadcastAs()
{
return 'app.server-created';
}
добавлено в 5.2 () 5.1 () 5.0 ()
Широковещательные данные
Когда событие широковещательное, все его public свойства автоматически сериализированы и передаются вместе с событием, что позволяет вам получить доступ к любым из его общедоступных данных из JavaScript приложения. Например, если у вашего события есть единственное общедоступное свойство $user, которое содержит Eloquent модель, широковещательные данные будут выглядеть так:
{ "user": { "id": 1, "name": "Jonathan Banks" ... } }
Однако, если вы хотите иметь еще более тщательный контроль над своими широковещательными данными, вы можете добавить к своему событию метод PHPbroadcastWith()
. Этот метод должен возвращать массив данных, которые вы хотите передать с событием:
/**
* Получить данные для передачи.
*
* @return array
*/
public function broadcastWith()
{
return ['user' => $this->user->id];
}
добавлено в 5.2 ()
Настройка широковещательных событий
По умолчанию имя широковещательного события — это полное имя класса этого события. Например, если имя класса App\Events\ServerCreated, то событие будет App\Events\ServerCreated. Имя можно изменить, определив метод PHPbroadcastAs()
в классе события:
/**
* Получение имени широковещательного события.
*
* @return string
*/
public function broadcastAs()
{
return 'app.server-created';
}
По умолчанию каждое широковещательное событие помещается в очередь по умолчанию для подключения по умолчанию в вашем файле настроек queue.php. Можно изменить очередь для вещания событий, добавив метод PHPonQueue()
в класс события. Этот метод должен возвращать имя нужной очереди:
/**
* Задание имени очереди для размещения событий.
*
* @return string
*/
public function onQueue()
{
return 'your-queue-name';
}
добавлено в 5.2 () 5.1 () 5.0 ()
Использование широковещательных событий
Вы можете удобно использовать широковещательную передачу событий, используя драйвер Pusher, используя Pusher SDK JavaScript. Например, давайте используем событие App\Events\ServerCreated из наших предыдущих примеров:
this.pusher = new Pusher('pusher-key');
this.pusherChannel = this.pusher.subscribe('user.' + USER_ID);
this.pusherChannel.bind('App\\Events\\ServerCreated', function(message) {
console.log(message.user);
});
Если вы будете использовать Redis, то вы должны будете написать свой собственный Redis потребитель типа издатель-подписчик, чтобы получать сообщения и широковещательно передавать их, используя websocket технологию на ваш выбор. Например, вы можете воспользоваться популярной библиотекой Socket.io, которая написана на Node.
Используя библиотеки Node socket.io и ioredis, вы можете быстро написать broadcaster событий, чтобы публиковать все события, которые широковещаются вашим приложением Laravel:
var app = require('http').createServer(handler);
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
app.listen(6001, function() {
console.log('Server is running!');
});
function handler(req, res) {
res.writeHead(200);
res.end('');
}
io.on('connection', function(socket) {
//
});
redis.psubscribe('*', function(err, count) {
//
});
redis.on('pmessage', function(subscribed, channel, message) {
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
Подписчики событий
Написание подписчиков событий
Подписчики событий — это классы, которые могут подписаться на множество событий из самого класса, что позволяет вам определить несколько обработчиков событий в одном классе. Подписчики должны определить метод PHPsubscribe()
, в который будет передан экземпляр диспетчера события. Вы можете вызвать метод PHPlisten()
на данном диспетчере для регистрации слушателей события:
<?php
namespace App\Listeners;
class UserEventSubscriber
{
/**
* Обработка события входа пользователя в систему.
*/
public function onUserLogin($event) {}
/**
* Обработка события выхода пользователя из системы.
*/
public function onUserLogout($event) {}
/**
* Регистрация слушателей для подписки.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'Illuminate\Auth\Events\Login',
'App\Listeners\UserEventSubscriber@onUserLogin'
);
$events->listen(
'Illuminate\Auth\Events\Logout',
'App\Listeners\UserEventSubscriber@onUserLogout'
);
}
}
Регистрация подписчика события
После написания подписчика, вы можете зарегистрировать его в диспетчере события. Вы можете зарегистрировать подписчиков, используя свойство $subscribe в EventServiceProvider. Например, давайте добавим UserEventSubscriber.
<?php
namespace App\Providers;
//для версии 5.2 и ранее:
//use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* Привязки слушателя события для приложения.
*
* @var array
*/
protected $listen = [
//
];
/**
* Классы подписчиков для регистрации.
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventSubscriber',
];
}
События фреймворка
Laravel предоставляет множество «базовых» событий для действий, выполняемых фреймворком. Вы можете подписаться на них таким же образом, как вы подписываетесь на свои собственные события:
Событие | Параметр(ы) |
---|---|
artisan.start | $application |
auth.attempt | $credentials, $remember, $login |
auth.login | $user, $remember |
auth.logout | $user |
cache.missed | $key |
cache.hit | $key, $value |
cache.write | $key, $value, $minutes |
cache.delete | $key |
connection.{name}.beganTransaction | $connection |
connection.{name}.committed | $connection |
connection.{name}.rollingBack | $connection |
illuminate.query | $query, $bindings, $time, $connectionName |
illuminate.queue.after | $connection, $job, $data |
illuminate.queue.failed | $connection, $job, $data |
illuminate.queue.stopping | null |
mailer.sending | $message |
router.matched | $route, $request |
{view name} | $view |
{view name} | $view |