Способы подключения поведений (behavior) в Yii2

13 января, 2015
Метки: ,

Поведение — это не только возможность повесить событие на действие, но еще и мощный инструмент, дающий неограниченные возможности повторного использования кода. Прелесть поведений в том, что крепиться они могут к чему угодно. Например: если все модели используют метод updateItem(), то его можно вынести в отдельный класс и отнаследовать от него все модели. А что делать если метод может быть не только у модели, но и у контроллера или представления? Что делать, если не все модели должны иметь доступ к общему методу? На помощь приходят поведения.
Я уже писала про поведения, про то, на какие события можно на них вешать. Надо бы расписать еще как самому создавать эти события, но сегодня поговорим о способах прикреплений поведений к объекту.

Способ 1: привязка из контроллера

//вызов функции:
$component->on($eventName, 'functionName');

//вызов метода functionName у объекта someClass
$component->on($eventName, ['someClass', 'functionName']);

//вызов анонимной функции:
$component->on($eventName, function ($event) {
...
});

Пример: прикрепим вызов метода на EVENT_AFTER_LOGIN для пользователя, который срабатывает после авторизации юзера:

Yii::$app->user->on(\yii\web\User::EVENT_AFTER_LOGIN,  ['\app\models\User', 'setUserInfoInSession'])

В данном примере после логина пользователя вызывается метод setUserInfoInSession в модели User

Способ 2: привязка на уровне объекта

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

 public function behaviors()
 {
   TimestampBehavior::className(),
   'thumbBehavior' => [
                'class' => ThumbBehavior::className(),
                'fileAttribute' => 'thumb',
                'saveDir' => '/web/upload/',
   ],
   'on '.\yii\web\User::EVENT_AFTER_LOGIN => function ($event) {
...
}
 }

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

class DateTimeBehavior extends AttributeBehavior
{
    public $dateTimeFields;
    public $format = 'd-m-Y H:i:s';

    public function events()
    {
        return [
            ActiveRecord::EVENT_AFTER_FIND => 'convertDate',
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'convertDateToDB',
        ];
    }
...
}

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

Способ 3: привязка из конфига

Прописать событие можно и в конфиге, в настройках компонентов.

'components' => [
'user' => [
            'identityClass' => 'app\models\User',
            'enableAutoLogin' => true,
            'on '.\yii\web\User::EVENT_AFTER_LOGIN => ['\app\models\User', 'setUserInfoInSession']
        ],
...
]

Подключать можно все теми-же 3 способами: в виде вызова глобальной функции, вызова метода у объекта либо в виде анонимной функции. В моем примере — вызов метода класса.
Осталось только напомнить, что само приложение — это тоже объект, у которого есть свои события. А значит к нему тоже можно подключать поведения

$config = [
...
    'components' => [
...
    ],
   'modules' => [
...
    ],
   'as AccessBehavior' => [
        'class' => AccessBehavior::className()
   ],
  'on'. \yii\web\Application::EVENT_AFTER_REQUEST => function ($event) {
...
}

Остались вопросы? Пишите в комментариях, а я постараюсь помочь.


Метки: ,

Оставить комментарий

15 комментариев »

    darlov

    Как то все сжато и не понятно

      Developer

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


    Rus

    Спасибо за пост! Но возникли некоторые вопросы…

    Не понятен вот этот код:
    $component->on($eventName, ‘functionName’);
    Что находится в переменной $component?

    И как создать свое событие EVENT_AFTER_LOGIN ?
    Т.е. мне хотелось бы создать событие, например ON_VIEW_ITEM (просмотр поста блога), чтоб зафиксировать факт просмотра поста в специальнои журнале (условно). Как подключить такое событие через поведение во все контроллеры (или в модели), где мне нужно отслеживать факт открытия этого поста? Можно скопипастить код во все контроллеры в метод actionView(), но это же некомильфо!

      Developer

      В переменной $component — компонент, к которому привязывается поведение. Компонент в контексте yii — это экземпляр какого-либо класса. Например Yii::$app->user — это компонент user, экземпляр класса yii\web\User. Те если вы хотите привязать поведение к событию своего класса, то компонентом будет экземпляр вашего класса.
      Как создать свое событие — отдельная обширная тема. Постараюсь описать это в одной из следующих статей. Пока скажу одно: не переопределив класс yii\web\User добавить свое событие не возможно. Но можно добавить свое поведение на одно из существующих событий.
      Запутанно получилось, объясняю на пальцах: есть объект (допустим кошка), у него есть серия встроенных событий (кошка проснулась, кошка проголодалась и тп). И у вас есть возможность добавлять действия на то или иное событие. Например: уведомить хозяина кошки, когда наступит событие «кошка проголодалась». Но вы не можете добавить события в кошку, вы можете только добавить действия в случаи наступления события. То же самое с классом User. У него есть событие, к примеру EVENT_AFTER_LOGIN, оно наступает, когда пользователь логинется. Вы можете добавить код, который будет срабатывать каждый раз при наступлении этого события:
      Yii::$app->user->on(\yii\web\User::EVENT_AFTER_LOGIN, [‘\app\models\User’, ‘setUserInfoInSession’])
      В моем примере этот код помещен в метод setUserInfoInSession, находящийся в моем классе \app\models\User. Если все еще не совсем понятно, почитайте про паттерн наблюдатель. В этом контексте поведения — это наблюдатели за объектом, на событие которого они подписываются.


    coobic

    Отличный пост, спасибо.
    Пытаюсь сделать собственный AccessBehavior на всё приложение, чтобы гостей перенаправлять на страничку входа и вижу странное поведение — behavior вызывается (метод привязанный через EVENT_BEFORE_ACTION) и вызывается проверка, а после нее хочется сделать redirect на форму вход:
    \Yii::$app->getResponse()->redirect($this->redirectUrl);
    но redirect происходит не сразу, а после выполнение всех методов в вызвавшем контроллере (в debug видно, что всё продолжает выполняться), чего по логике не должно быть (зачем выполнять что-то на странице, куда у пользователя и доступа быть не может, и где его имя может использоваться для чего-либо).
    Пробовал возвращать false или true в методе, который привязан к EVENT_BEFORE_ACTION, но ничего не помогает.
    Не подскажете в какую сторону копать?


    coobic

    В дополнение к вышестоящему, понятно, что:
    \Yii::$app->getResponse()->redirect($this->redirectUrl);
    только добавляет Location к заголовку.
    И получилось победить только костылем:
    \Yii::$app->getResponse()->redirect($this->redirectUrl)->send();
    exit(0);
    Возможно, существует, более правильный путь?

      Developer

      Более правильный путь — бросать Exception и обрабатывать его так как хочется (например редиректом на стр). У меня есть простенький пример такого обработчика.


    Танков Денис

    Непонятна конкретно взаимосвязь между частями объяснений и кусками кода.

    10 минут жизни потрачено впустую — когда человек читает -он пытается вникнуть — потом уже лезет в комментарии — такой обширный коммент как у Вас должен побудить дописать его в статью ибо его наличие указывает на серьезные недостатки Вашего поста

      Developer

      Статья про то, как привязывать поведение к объекту, т.е подразумевается, что вы уже знаете что такое поведение. И статья вам поможет узнать еще несколько способов как их привязывать к объектам. Если вы не знаете, что такое поведение — то статья вам ничем не поможет.

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


    Антон

    Добрый день!

    У вас есть такая строчка в конфиге
    «as AccessBehavior»

    Вот про это могли бы рассказать подробнее?

      Developer

      Это поведение, повешенное на уровне приложения. Подробнее о поведениях есть тут. Если кратко — то поведение — это возможность выполнить какой-то кусок кода при наступлении события, вклинить свой кусок кода в работу приложения Yii. К примеру мое поведение вклинивается между обработкой запроса от пользователя и передачей управления в контроллер. Т.е мой кусок кода обрабатывается после того, как Yii фреимворк обработал запрос, нашел правильный маршрут кто этот запрос должен отработать, но до того как action начал выполняться. Надеюсь стало понятнее?


    fun4life

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

      Developer

      Статья о подключении своих методов к существующим поведениям. О том как создать свои события в статье ни слова. Спасибо за идею, допишу статью.


    TkVolly

    Добрый вечер
    Посоветуйте как правильно сделать.
    Есть 2 модуля:
    В 1ом модель User, а во 2ом SomeBehavior
    Надо добавить это поведение не для конкретного пользователя $user, а для всех, как это было-бы добавив его просто в метод behaviors() модели User.
    При этом 1ый модуль не зависим от 2ого

    Так вот я вижу 2 способа:
    1) Вывести поведения модели User в конфиги:
    в грубом виде что-то типа:
    class User {

    behaviors () {
    return array_merge([

    ], Yii::$app->params[‘userBehaviors’]);
    }
    }

    а в Модуле2:
    class Module2 implements BootstrapInterface
    {
    public function bootstrap($app) {
    $app->params[‘userBehaviors’] ?? [];
    $app->params[‘userBehaviors’][‘myBehavior’] = MyBehavior::className();
    }
    }

    И 2 способ отлавливать событие AFTER_FIND у пользователя, и добавлять через attachBehavior()
    но этот способ кажется хуже чем первый

      Developer

      Второй способ менее прозрачный чем первый, первый мне нравится больше всего. Подброщу еще идею: добавить вызов события в behaviors ($this->trigger(…)) и дать возможность кому угодно вещать свои обработчики. Тоже не прозрачно, зато более гибко (поведение можно вещать без прописки в конфиге, бывает полезно для интеграции двух сторонних модулей, когда переписывать класс любого из них можно только наследованием).


Оставить комментарий:









Копирование материалов разрешено при наличии активной ссылки на источник