Итак, узнал сегодня небольшую фитчу Laravel/Eloquent, которая практически не описана и лишь вскользь упомянута в документации фреймовика.

Перейдите к TL;DR, если вы просто хотите увидеть эту фичу.

Возможно, вы уже знаете, что можно добавлять статические boot()-методы к Eloquent-модели, которые будут выполнены при ее загрузке. Это можно использовать, например, привязываясь к событиям модели, если это необходимо. Например, вы можете организовать email-уведомление, каждый раз, когда будет создан новый пользователь(неудачный пример), но вы могли бы описать это следующим образом:
    class User extends Eloquent {
        public static function boot()
        {
            parent::boot();
            static::created(function($user) {
                // Send a mailing…
            });
        }
    }

Но что если вы хотите поместить это с trait?

Рассмотрим такой сценарий; необходимо организовать поиск в диапазоне моделей. Конечно, можно создать новый класс SearchableModel, унаследовав его от Eloquent\Model, но хотелось бы написать таким образом, чтобы функционал был легко переносим в другие проекты, не привязываясь к конкретному приложению. Трейт здесь подойдет как-никак лучше, потому что он легко переносим и относительно ненавязчив, подключая который вы получаете дополнительный функционал, который может быть легко переопределен.

Итак, я создал SearchableTrait.
     trait SearchableTrait {
	
         public function search($query)
         {
             // ...
         } 
    }

Довольно просто пока, мы вызываем метод $model->search('query'), возвращающий модели, соответствующие критериям запроса.

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

Но куда мне вставить этот код?

Я мог бы поместить его в boot()-метод модели, но там он может быть затерт, тем самым нарушая мой поиск и уничтожая любой шанс быть повторно использованным.

TL; DR

Ребята из Laravel явно предусмотрели, каким образом подгружать ваши трейты, определяя специальное метод, как в Eloquent-модели, к примеру:
    trait SearchableTrait {
	
        public function search($query)
        {
            // ...
        }
	
        public static function bootSearchableTrait()
        {
            static::created(function($item){
                // Index the item
             });
        }
    }

Таким образом, здесь присутствует небольшая классическая магия Eloquent. Если у вас есть статический метод в трейте, который называется по принципу boot[NameOfTrait], то он будет выполнен также, как статический boot()-метод модели. Какое удобное место для регистрации событий своей модели.

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

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

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


  1. mark_ablov
    05.06.2015 10:02

    > Eloquent-модели, которые будут выполнены при ее загрузке
    Это не совсем верно, boot-методы (обычный и для трейтов) вызываются только один раз для модели, а не при каждом инстанциировании.


    1. SerafimArts
      05.06.2015 11:22

      > Это не совсем верно, boot-методы (обычный и для трейтов) вызываются только один раз для модели

      В тестовом окружении метод boot вызывается только для самого первого юнит-теста. В остальных — просто игнорируется. Так что предлагаю быть с ними (с методами boot) аккуратнее.


      1. mark_ablov
        05.06.2015 11:44

        > В остальных — просто игнорируется
        1. Поведение этого механизма не зависит от окружения.
        2. Вызывается не для первого юнит-теста, а для первого созданного объекта модели.
        По крайней мере в 4 версии это так:

        код Model
        	public function __construct(array $attributes = array())
        	{
        		$this->bootIfNotBooted();
        		$this->syncOriginal();
        		$this->fill($attributes);
        	}
        
        	/**
        	 * Check if the model needs to be booted and if so, do it.
        	 *
        	 * @return void
        	 */
        	protected function bootIfNotBooted()
        	{
        		$class = get_class($this);
        		if ( ! isset(static::$booted[$class]))
        		{
        			static::$booted[$class] = true;
        			$this->fireModelEvent('booting', false);
        			static::boot();
        			$this->fireModelEvent('booted', false);
        		}
        	}
        


        1. SerafimArts
          05.06.2015 13:18
          +1

          А внутри TestCase setUp метод релоада приложения, который уничтожает все данные (в т.ч. создаёт новый объект эвент листнера, если в boot прописан, например `static::updated` — его не будет в следующем тесте), но оставляет булев `booted`. Так что во втором+ тесте вызов не производится.

          Верное замечание, что не зависит от окружения. Просто тестирование реализовано так, что boot нормально не работает и можно получить себе граблями в лоб.


  1. SerafimArts
    05.06.2015 13:17

    *промахнулся веточкой*


  1. greabock
    08.06.2015 07:21

    Эм… работа моделей в ларе — это повод для дискуссии, но вряд ли для поста на хабр. Да еще и такой короткий… Да еще и перевод… плохой выбор, в общем.