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

Событийная модель Laravel: эвенты в Eloquent и всей системы в целом

Event Observer События Eloquent Laravel 4.x

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

Вообще, что такое событие в программировании? И вот что говорит википедия:

Событие это сообщение которое возникает в различных участках исполняемого кода при выполнении определенных условий.

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

В базах данных существует такое понятие как триггер. Т.е. некая хранимая процедура вызываемая при наступлении определенных событий. Например, добавление, удаление или обновление записей в таблице. Помимо этого, для одного и того же события может быть создано 2 триггера — до наступления события и после.

В Eloquent ORM точно так же. Creating — вызывается до наступления события создания записи. А created – после. Логично предположить, что всего в ORM 10 событий:

Действие Событие до Событие после
Создание Creating Created
Обновление Updating Updated
Сохранение Saving Saved
Удаление Deleting Deleted
Восстановление Restoring Restored

Но не тут то было. Их 11. Существует еще событие boot, которое вызывается в момент получения объекта. Я бы не назвал это событием — чистой воды ООП. Но раз об этом упомянуто в документации — то стоит затронуть.

Идея с boot состоит в том, что мы расширяем базовый метод Eloquent::boot дополняя его своей логикой. Вот пример расширения абстрактной модели Test

Class Test extends Eloquent
{ 
    public static function boot()
    { 
        Log::info('Before boot'); 
        parent::boot(); 
        Log::info('After boot'); 
    } 
}

Теперь давайте рассмотрим как можно использовать события по прямому их назначению:

// Регистрация триггера с валидацией атрибута name 
Test::creating(function($test) {
    Log::info('Before closure'); 
    return is_scalar($test->name) && !empty($test->name); //валидация поля 
}); 
// Атрибут валиден Test::create(array( 'name' => 'test' )); // true 
// Кривой атрибут Test::create(array( 'name' => array('error?') )); //false

Идем далее. И посмотрим на ООП стиль событийности для создания/удаления/редактирования записей. Т.е. По аналогии с методом boot прямо в самой модели переопределим методы create, update или delete:

public static function create(array $attr = array())
{ 
    Log::info('Before in model');
    $out = (!empty($attr['name']) && is_scalar($attr['name']) ? parent::create($attr) : false;
    Log::info('After in model');
    return $out;
}

С точки зрения архитектуры проекта, такой подход более понятен на мелком и среднем проекте, т.к. все что относится к модели — находится внутри нее самой. А вот предыдущий способ и все последующие рекомендуется выносить, например, в файл /app/events.php по аналогии с /app/filters.php, который подключается при старте laravel. Т.е. из файла /app/start/global.php.

Итак, следующий способ зарегистрировать триггер — при помощи модели наблюдения. А вот регистрировать данный наблюдатель можно как динамически в зависимости от условий, так и статично при помощи метода boot:

Class Test extends Eloquent
{
    public static function boot()
    {
        self::observe(new TestObserver); 
        parent::boot();
    }
}
class TestObserver
{
    public function creating($test)
    {
        Log::info('Before observer create');
    }
    public function created($test)
    {
        Log::info('After observer create');
    }
}

При всем при этом, никто не запрещает совмещать способы создания триггеров. Т.е. фактически, мы уже знаем 3 разных способа задания события для before create, after create. Точно так же и для update, delete.

Ну и остался последний, четвертый способ наблюдения за событиями модели. Последний и самый мощный на мой взгляд — Event::listen:

Event::listen('eloquent.creating: Test', function($obj)){ Log::info($obj); });

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

Event::listen('eloquent.*', function($obj)){ Log::info($obj); });

Вторая, самая важная особенность фасада Event в том, что он позволяет создавать произвольные события. Допустим, регистрация нового пользователя. Авторизация в админке и т.д. и т.п. Делается это очень просто при помощи метод fire:

Event::fire('user.reg', $user);

Ну а слушать через Event::listen мы уже умеем.

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

Event::queue('user.reg', , function()){ });

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

Event::subscribe(new UserEventHandler);

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

С событиями на этом все. Но перехват ошибок своего рода тоже события. Поэтому хочу хотя бы кратко и об этом рассказать.

На самом деле, тут все просто:

App::error(function(Exception $e){ 
    //действие
});

Но не на одном перехватчике проекты строятся. Ведь в проектах может быть много исключений. Начиная с некорректного SQL-запроса и заканчивая отсутствием прав доступа. Соответственно логично отдавать для разных исключений разные ошибки. И тут на помощь приходит контроль типов.

Вообще, регистрация ошибок происходит в файле app/start/global.php, если таковых много, то желательно вынести в отдельный файл как и события - допустим в app/errors.php.

Помимо метода error есть еще down – синоним для события illuminate.app.down. Говорящее название — при завершении работы. Если сделать поиск по методу listen в папке vendor/laravel/framework, то можно найти упоминания о таких событиях, как:

  • artisan.start
  • illuminate.queue.failed
  • illuminate.log
  • illuminate.app.down
  • illuminate.query
  • router.matched
  • router.{filter}
  • router.filter: {filter}
  • auth.attempt

Ну и раз уж затронули консольную команду artisan, то грех не затронуть события composer-а. Ведь Laravel ставится, обновляется и устанавливает компоненты именно через него. Поэтому composer можно считать по праву частью laravel.

Для тех, кто не обращал внимание, composer поддерживает целых аж 18 событий до начала или после установки, обновления или удаления пакета или самого проекта. Данные события указываются в секции scripts файла composer.json. И после установки laravel там автоматически регистрируются 3 события разобрать которые я вам всем предлагаю уже самостоятельно...

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

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

Vijit

Непонятно написано, имхо. Сложно читать. Нехатает, как минимум, пространств имен, чтобы осознать, куда относится приведенный класс. Это

  1. А вот предыдущий способ и все последующие рекомендуется выносить

вообще вынесло мозг. Хотя бы нумерацию какую-то ввел (html: ordered list) или разделил статью подзаголовками. Хоть что-то. Хрен поймешь, где текущий способ, какой считать предыдущим и т.п.

UPD Перечитал еще раз, после копания в мануале. Стало понятнее, но все равно туго доходит.

skv

Крутая статья, большое спасибо!!!

Аффтар ацкей сотона пешы ищщо )))

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

Разметка: ? ?

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