# Поведения ¶

Поведения (behaviors) — это экземпляры класса yii\base\Behavior или класса, унаследованного от него. Поведения, также известные как примеси, позволяют расширять функциональность существующих компонентов без необходимости изменения дерева наследования. После прикрепления поведения к компоненту, его методы и свойства "внедряются" в компонент, и становятся доступными так же, как если бы они были объявлены в самом классе компонента. Кроме того, поведение может реагировать на события, создаваемые компонентом, что позволяет тонко настраивать или модифицировать обычное выполнение кода компонента.

## Создание поведений ¶

Поведения создаются путем расширения базового класса yii\base\Behavior или его наследников. Например,

    namespace app\components;

    use yii\base\Behavior;

    class MyBehavior extends Behavior
    {
        public $prop1;

        private $_prop2;

        public function getProp2()
        {
            return $this->_prop2;
        }

        public function setProp2($value)
        {
            $this->_prop2 = $value;
        }

        public function foo()
        {
            // ...
        }
    }

В приведенном выше примере, объявлен класс поведения `app\components\MyBehavior` содержащий 2 свойства `prop1` и `prop2`, и один метод `foo()`. Обратите внимание, свойство `prop2` объявлено с использованием геттера `getProp2()` и сеттера `setProp2()`. Это возможно, так как yii\base\Behavior является дочерним классом для yii\base\BaseObject, который предоставляет возможность определения свойств через геттеры и сеттеры.

Так как этот класс является поведением, когда он прикреплён к компоненту, компоненту будут также доступны свойства `prop1` и `prop2`, а также метод `foo()`.

> Подсказка: Внутри поведения возможно обращаться к компоненту, к которому оно прикреплено, используя свойство yii\base\Behavior::$owner.

> Примечание: При переопределении метода поведения yii\base\Behavior::__get() и/или yii\base\Behavior::__set() необходимо также переопределить yii\base\Behavior::canGetProperty() и/или yii\base\Behavior::canSetProperty().

## Обработка событий компонента ¶

Если поведению требуется реагировать на события компонента, к которому оно прикреплено, то необходимо переопределить метод yii\base\Behavior::events(). Например,

    namespace app\components;

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

    class MyBehavior extends Behavior
    {
        // ...

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

        public function beforeValidate($event)
        {
            // ...
        }
    }

Метод events() должен возвращать список событий и соответствующих им обработчиков. В приведенном выше примере, объявлено событие EVENT_BEFORE_VALIDATE и его обработчик `beforeValidate()`. Указать обработчик события, можно одним из следующих способов:

- строка с именем метода текущего поведения, как в примере выше;
- массив, содержащий объект или имя класса, и имя метода, например, `[$object, 'methodName']`;
- анонимная функция.

Функция обработчика события должна выглядеть как показано ниже, где `$event` содержит параметр события. Более детальная информация приведена в разделе События.

    function ($event) {
    }

## Прикрепление поведений ¶

Прикрепить поведение к компоненту можно как статически, так и динамически. На практике чаще используется статическое прикрепление.

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

    namespace app\models;

    use yii\db\ActiveRecord;
    use app\components\MyBehavior;

    class User extends ActiveRecord
    {
        public function behaviors()
        {
            return [
                // анонимное поведение, прикрепленное по имени класса
                MyBehavior::className(),

                // именованное поведение, прикрепленное по имени класса
                'myBehavior2' => MyBehavior::className(),

                // анонимное поведение, сконфигурированное с использованием массива
                [
                    'class' => MyBehavior::className(),
                    'prop1' => 'value1',
                    'prop2' => 'value2',
                ],

                // именованное поведение, сконфигурированное с использованием массива
                'myBehavior4' => [
                    'class' => MyBehavior::className(),
                    'prop1' => 'value1',
                    'prop2' => 'value2',
                ]
            ];
        }
    }

Вы можете связать имя с поведением, указав его в качестве ключа элемента массива, соответствующего конфигурации поведения. В таком случае, поведение называется именованным. В примере выше, два именованных поведения: `myBehavior2` и `myBehavior4`. Если с поведением не связано имя, такое поведение называется анонимным.

Для того, чтобы прикрепить поведение динамически, необходимо вызвать метод yii\base\Component::attachBehavior() требуемого компонента:

    use app\components\MyBehavior;

    // прикрепляем объект поведения
    $component->attachBehavior('myBehavior1', new MyBehavior);

    // прикрепляем по имени класса поведения
    $component->attachBehavior('myBehavior2', MyBehavior::className());

    // прикрепляем используя массив конфигураций
    $component->attachBehavior('myBehavior3', [
        'class' => MyBehavior::className(),
        'prop1' => 'value1',
        'prop2' => 'value2',
    ]);

Использование метода yii\base\Component::attachBehaviors() позволяет прикрепить несколько поведений за раз. Например,

    $component->attachBehaviors([
        'myBehavior1' => new MyBehavior,  // именованное поведение
        MyBehavior::className(),          // анонимное поведение
    ]);

Так же, прикрепить поведение к компоненту можно через конфигурацию, как показано ниже:

    [
        'as myBehavior2' => MyBehavior::className(),

        'as myBehavior3' => [
            'class' => MyBehavior::className(),
            'prop1' => 'value1',
            'prop2' => 'value2',
        ],
    ]

Более детальная информация приведена в разделе Конфигурации.

## Использование поведений ¶

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

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

    // публичное свойство "prop1" объявленное в классе поведения
    echo $component->prop1;
    $component->prop1 = $value;

Аналогично, вы можете вызывать публичные методы поведения,

    // публичный метод foo() объявленный в классе поведения
    $component->foo();

Обратите внимание, хотя `$component` не имеет свойства `prop1` и метода `foo()`, они могут быть использованы, как будто являются членами этого класса.

В случае, когда два поведения, имеющие свойства или методы с одинаковыми именами, прикреплены к одному компоненту, преимущество будет у поведения, прикрепленного раньше.

Если при прикреплении поведения к компоненту указано имя, можно обращаться к поведению по этому имени, как показано ниже:

    $behavior = $component->getBehavior('myBehavior');

Также можно получить все поведения, прикрепленные к компоненту:

    $behaviors = $component->getBehaviors();

## Отвязывание поведений ¶

Чтобы отвязать поведение от компонента, необходимо вызвать метод yii\base\Component::detachBehavior(), указав имя, связанное с поведением:

    $component->detachBehavior('myBehavior1');

Так же, возможно отвязать все поведения:

    $component->detachBehaviors();

## Использование поведения `TimestampBehavior` ¶

В заключении, давайте посмотрим на yii\behaviors\TimestampBehavior — поведение, которое позволяет автоматически обновлять атрибуты с метками времени при сохранении Active Record моделей через `insert()`, `update()` или `save()`.

Для начала, необходимо прикрепить поведение к классу Active Record, в котором это необходимо:

    namespace app\models\User;

    use yii\db\ActiveRecord;
    use yii\behaviors\TimestampBehavior;

    class User extends ActiveRecord
    {
        // ...

        public function behaviors()
        {
            return [
                [
                    'class' => TimestampBehavior::className(),
                    'attributes' => [
                        ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                        ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                    ],
                    // если вместо метки времени UNIX используется datetime:
                    // 'value' => new Expression('NOW()'),
                ],
            ];
        }
    }

Конфигурация выше описывает следующее:

- при вставке новой записи поведение должно присвоить текущую метку времени UNIX атрибутам `created_at` и `updated_at`;
- при обновлении существующей записи поведение должно присвоить текущую метку времени UNIX атрибуту `updated_at`.

> Примечание: Для того, чтобы приведённая выше конфигурация работала с MySQL, тип `created_at` и `updated_at` должен быть `int(11)`. В нём будет храниться UNIX timestamp.

Теперь, если сохранить объект `User`, то в его атрибуты `created_at` и `updated_at` будут автоматически установлены значения метки времени UNIX на момент сохранения записи:

    $user = new User;
    $user->email = 'test@example.com';
    $user->save();
    echo $user->created_at;  // выведет метку времени на момент сохранения записи

Поведение TimestampBehavior так же содержит полезный метод touch(), который устанавливает текущую метку времени указанному атрибуту и сохраняет его в базу данных:

    $user->touch('login_time');

## Другие поведения ¶

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

- yii\behaviors\BlameableBehavior - автоматически заполняет указанные атрибуты ID текущего пользователя.
- yii\behaviors\SluggableBehavior - автоматически заполняет указанные атрибут пригодным для URL текстом, получаемым из другого атрибута.
- yii\behaviors\AttributeBehavior - автоматически задаёт указанное значение одному или нескольким атрибутам ActiveRecord при срабатывании определённых событий.
- yii2tech\ar\softdelete\SoftDeleteBehavior - предоставляет методы для «мягкого» удаления и воосстановления ActiveRecord. То есть выставляет статус или флаг, который показывает, что запись удалена.
- yii2tech\ar\position\PositionBehavior - позволяет управлять порядком записей через специальные методы. Информация сохраняется в целочисленном поле.

## Сравнение с трейтами ¶

Несмотря на то, что поведения схожи с трейтами тем, что "внедряют" свои свойства и методы в основной класс, они имеют множество отличий. Они оба имеют свои плюсы и минусы, и, скорее, дополняют друг друга, а не заменяют.

### Плюсы поведений ¶

Поведения, как и любые другие классы, поддерживают наследование. Трейты можно рассматривать как копипейст на уровне языка. Они наследование не поддерживают.

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

Поведения, в отличие от трейтов, можно настраивать.

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

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

### Плюсы трейтов ¶

Трейты являются гораздо более производительными, чем поведения, которые, являясь объектами, требуют дополнительного времени и памяти.

Многие IDE поддерживают работу с трейтами, так как они являются стандартными конструкциями языка.