19События
Как подписаться на событие, вызвать своё, передать данные, остановить цепочку обработчиков; события уровня класса, интерфейса и глобальные.
Событие — точка, в которой компонент говорит «случилось вот это», а любой код может отреагировать. Компонент вызывает trigger(), подписчики регистрируются через on(). Так фреймворк даёт встраиваться в свою работу, не требуя наследования.
Подписка#
$foo->on(Foo::EVENT_HELLO, 'function_name'); // глобальная функция
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); // метод объекта
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'method']); // статический метод
$foo->on(Foo::EVENT_HELLO, function ($event) { // замыкание
echo $event->name; // имя события
echo $event->sender; // кто вызвал trigger()
echo $event->data; // данные, переданные при подписке
});
// данные для обработчика — третий аргумент
$foo->on(Foo::EVENT_HELLO, function ($event) { echo $event->data; }, 'abc');
// в начало очереди, а не в конец
$foo->on(Foo::EVENT_HELLO, $handler, null, false);Через конфигурацию — ключ 'on имяСобытия':
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'on afterOpen' => function ($event) {
$event->sender->createCommand("SET time_zone = '+00:00'")->execute();
},
],
],Порядок и остановка#
Обработчики вызываются в порядке подписки. Чтобы остановить остальные:
$foo->on(Foo::EVENT_HELLO, function ($event) {
$event->handled = true; // следующие обработчики не вызовутся
});Многие события фреймворка отменяют операцию через $event->isValid = false (beforeSave, beforeAction, beforeLogin…).
Вызов своего события#
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
public $message; // свои данные — в наследнике Event
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = 'messageSent'; // имя — константой: без опечаток, видно в IDE
public function send($message)
{
// ...отправка...
$this->trigger(self::EVENT_MESSAGE_SENT, new MessageEvent(['message' => $message]));
}
}Класс должен наследовать yii\base\Component. Без объекта события trigger() создаст пустой Event.
Отписка#
$foo->off(Foo::EVENT_HELLO, 'function_name');
$foo->off(Foo::EVENT_HELLO, $anonymousFunction); // замыкание нужно сохранить в переменную при подписке
$foo->off(Foo::EVENT_HELLO); // все обработчики событияСобытия уровня класса#
Слушать событие всех экземпляров класса и его потомков — статический Event::on():
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::debug(get_class($event->sender) . ' добавлен');
});
Event::trigger(Foo::class, Foo::EVENT_HELLO); // событие без объекта: $event->sender === null
Event::off(Foo::class, Foo::EVENT_HELLO);Порядок: сначала обработчики экземпляра, затем — класса. Осторожно с базовыми классами вроде BaseObject: подписка на них сработает для всего приложения.
На уровне интерфейса#
Первым аргументом Event::on() может быть интерфейс — обработчик получит события всех классов, которые его реализуют:
interface DanceEventInterface { const EVENT_DANCE = 'dance'; }
Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::debug(get_class($event->sender) . ' танцует');
});Вызвать Event::trigger() для интерфейса нельзя — только для конкретного класса.
Глобальные события#
Никакого отдельного механизма: событие вешают на синглтон — приложение:
Yii::$app->on('app.mail.sent', function ($event) { /* ... */ });
Yii::$app->trigger('app.mail.sent', new Event(['sender' => $mailer]));Пространство имён одно на всё приложение — давайте событиям осмысленные префиксы.
Где события уже есть#
beforeRequest, afterRequest, beforeAction, afterActionbeforeAction, afterActionbeforeValidate, afterValidateinit, afterFind, beforeInsert/Update/Delete, afterInsert/Update/Delete, afterRefreshbeforeRender, afterRender, beginPage, endPage, beginBody, endBodybeforeLogin, afterLogin, beforeLogout, afterLogoutbeforeSend, afterPrepare, afterSendafterOpen, beginTransaction, commitTransaction, rollbackTransactionНаследовать yii\base\Component и вызывать $this->trigger($name, $event).
Установить $event->handled = true в обработчике.
Первый слушает конкретный объект; второй — все экземпляры класса и его потомков (и вызывается после обработчиков экземпляра).
Аргументом $data: третьим у $obj->on() и четвёртым у Event::on(); они доступны как $event->data. Данные, известные в момент события, передают через свой класс Event в trigger().
Ключом 'on имяСобытия' => $handler в массиве конфигурации компонента.