Когда вы пишете на Yii2, рано или поздно упираетесь в момент, когда нужно добавить функциональность сразу нескольким моделям или контроллерам. Можно, конечно, скопировать код, но это путь в никуда. На помощь приходят события и поведенческие примеси (behaviors). Они позволяют расширять классы без изменения их кода. Звучит сложно? На самом деле, это как конструктор LEGO: добавляете нужные блоки — и всё работает.

Digital-студия WNDER

Что такое события и зачем они нужны

События — это способ сказать: «Когда произойдёт вот это, сделай то». В Yii2 события реализованы на уровне компонента, а так как почти всё в Yii2 — компоненты, то события доступны везде: в моделях, контроллерах, компонентах.

Представьте, что у вас есть интернет-магазин. Когда пользователь оформляет заказ, вы хотите отправить ему письмо, обновить склад и начислить бонусы. Вместо того чтобы писать весь этот код в методе `createOrder()`, вы можете просто вызвать событие `order_created` и подписаться на него в трёх разных местах. Код становится чище, а добавить новое действие (например, отправить уведомление в Telegram) можно вообще не трогая логику заказа.


// В модели Order
class Order extends \yii\db\ActiveRecord
{
    const EVENT_ORDER_CREATED = 'order_created';

    public function createOrder($data)
    {
        // ... логика сохранения ...
        $this->trigger(self::EVENT_ORDER_CREATED);
    }
}

А вот так подписываемся на событие:


// В контроллере или где-то ещё
$order = new Order();
$order->on(Order::EVENT_ORDER_CREATED, function($event) {
    // Отправляем письмо
    Yii::$app->mailer->compose('order-created', ['order' => $event->sender])
        ->setTo($event->sender->email)
        ->send();
});

В обработчик события передаётся объект `yii\base\Event`, у которого есть свойство `sender` — это объект, который вызвал событие. Удобно, правда?

Поведенческие примеси: что это и как упрощают жизнь

Поведенческие примеси (behaviors) — это способ добавить методы и свойства в класс без наследования. В Yii2 поведение — это объект, который прикрепляется к компоненту и «вливает» в него свои методы. Классический пример — поведение `TimestampBehavior`, которое автоматически заполняет поля `created_at` и `updated_at`.

Допустим, у вас есть модель `Post`. Вы хотите, чтобы при сохранении автоматически проставлялась дата создания. Вместо того чтобы писать это в каждом методе, вы просто подключаете поведение:


use yii\behaviors\TimestampBehavior;

class Post extends \yii\db\ActiveRecord
{
    public function behaviors()
    {
        return [
            TimestampBehavior::className(),
        ];
    }
}

Всё. Теперь при сохранении модели поля `created_at` и `updated_at` заполнятся автоматически. Поведение можно настроить под себя, например, указать, какие поля заполнять:


public function behaviors()
{
    return [
        [
            'class' => TimestampBehavior::className(),
            'createdAtAttribute' => 'created_at',
            'updatedAtAttribute' => 'updated_at',
            'value' => function() { return date('Y-m-d H:i:s'); },
        ],
    ];
}

Поведения — это не только про время. Вы можете написать своё поведение, которое добавляет, например, методы для работы с тегами или рейтингами. Это позволяет переиспользовать код между разными моделями.

Создаём своё поведение

Давайте напишем простое поведение, которое автоматически генерирует slug (человеко-понятный URL) для модели. Например, для новостей или статей.


namespace app\components;

use yii\base\Behavior;
use yii\db\ActiveRecord;
use yii\helpers\Inflector;

class SlugBehavior extends Behavior
{
    public $sourceAttribute = 'title';
    public $slugAttribute = 'slug';

    public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'generateSlug',
        ];
    }

    public function generateSlug($event)
    {
        $model = $this->owner;
        if (empty($model->{$this->slugAttribute})) {
            $model->{$this->slugAttribute} = Inflector::slug($model->{$this->sourceAttribute});
        }
    }
}

Теперь подключаем его к модели:


class Post extends \yii\db\ActiveRecord
{
    public function behaviors()
    {
        return [
            [
                'class' => SlugBehavior::className(),
                'sourceAttribute' => 'title',
                'slugAttribute' => 'slug',
            ],
        ];
    }
}

Теперь при создании записи, если slug не заполнен, он сгенерируется автоматически из заголовка. Обратите внимание, что мы используем событие `EVENT_BEFORE_VALIDATE` — оно срабатывает перед проверкой данных. Это удобно, потому что мы можем быть уверены, что slug будет готов к моменту сохранения.

События в поведении: как они работают

Поведения могут не только добавлять методы, но и реагировать на события владельца. В методе `events()` мы возвращаем массив, где ключ — имя события, а значение — метод поведения, который будет вызван.

Например, если мы хотим логировать все изменения модели, мы можем создать поведение, которое подписывается на событие `EVENT_AFTER_UPDATE`:


class LogBehavior extends Behavior
{
    public function events()
    {
        return [
            ActiveRecord::EVENT_AFTER_UPDATE => 'logUpdate',
        ];
    }

    public function logUpdate($event)
    {
        // Логируем изменения
        Yii::info('Модель обновлена: ' . $this->owner->className(), 'model');
    }
}

И прикрепляем его к модели:


public function behaviors()
{
    return [
        LogBehavior::className(),
    ];
}

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

Практические советы и лайфхаки

Давайте подведём итоги и дадим несколько советов, которые пригодятся в реальной работе.

Правило: Используйте события для действий, которые не являются основной логикой модели. Например, отправка писем, обновление статистики, уведомления. Основную логику оставляйте в методах модели, а побочные эффекты — в обработчиках событий.

Вот ещё несколько полезных приёмов:

  • Не злоупотребляйте событиями. Если событие вызывает цепочку из десяти обработчиков, это может запутать. Старайтесь, чтобы обработчиков было немного и они были логичными.
  • Используйте поведения для переиспользования кода. Если вы замечаете, что одна и та же логика повторяется в нескольких моделях, — вынесите её в поведение.
  • События можно вызывать цепочкой. Вы можете вызвать событие внутри обработчика другого события, но будьте осторожны — легко получить бесконечный цикл.
  • Проверяйте, есть ли уже готовые поведения. В Yii2 есть много встроенных поведений: `TimestampBehavior`, `BlameableBehavior`, `SluggableBehavior` (в расширениях). Прежде чем писать своё, поищите готовое.

Сравним, когда что использовать:

Критерий События Поведения
Что делают Реагируют на действия Добавляют функциональность
Когда использовать Когда нужно выполнить дополнительные действия при событии Когда нужно добавить методы или свойства классу
Пример Отправка письма после создания заказа Автозаполнение даты создания

Ещё один лайфхак: если вы используете события в своих моделях, обязательно документируйте их. Имена событий лучше выносить в константы класса, чтобы не ошибиться при подписке.

Что в итоге

События и поведенческие примеси — это мощные инструменты Yii2, которые помогают писать чистый, модульный и легко расширяемый код. События позволяют развязать логику, а поведения — переиспользовать функциональность. Освоив их, вы значительно ускорите разработку и упростите поддержку проектов.

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

Удачи в кодинге!

Студия WNDER