Back to Yii2

Concept Behaviors

docs/guide-ru/concept-behaviors.md

2.0.5419.1 KB
Original Source

Поведения

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

Создание поведений <span id="defining-behaviors"></span>

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

php
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, содержащий свойства prop1 и prop2, а также метод foo(). Обратите внимание: свойство prop2 объявлено с использованием геттера getProp2() и сеттера setProp2(). Это возможно, так как [[yii\base\Behavior]] является дочерним классом для [[yii\base\BaseObject]], который предоставляет возможность определения свойств через геттеры и сеттеры.

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

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

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

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

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

php
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)
    {
        // ...
    }
}

Метод [[yii\base\Behavior::events()|events()]] должен возвращать список событий и соответствующих им обработчиков. В приведённом выше примере объявлено событие [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] и его обработчик beforeValidate(). Указать обработчик события можно одним из следующих способов:

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

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

php
function ($event) {
}

Прикрепление поведений <span id="attaching-behaviors"></span>

Прикрепить поведение к [[yii\base\Component|компоненту]] можно как статически, так и динамически. На практике чаще используется статическое прикрепление.

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

php
namespace app\models;

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

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

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

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

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

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

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

php
use app\components\MyBehavior;

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

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

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

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

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

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

php
[
    'as myBehavior2' => MyBehavior::class,

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

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

Использование поведений <span id="using-behaviors"></span>

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

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

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

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

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

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

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

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

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

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

php
$behaviors = $component->getBehaviors();

Отвязывание поведений <span id="detaching-behaviors"></span>

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

php
$component->detachBehavior('myBehavior1');

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

php
$component->detachBehaviors();

Использование поведения TimestampBehavior <span id="using-timestamp-behavior"></span>

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

Для начала, необходимо прикрепить поведение к классу [[yii\db\ActiveRecord|Active Record]], в котором это необходимо:

php
namespace app\models\User;

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

class User extends ActiveRecord
{
    // ...

    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::class,
                '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.

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

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

php
$user = new User;
$user->email = '[email protected]';
$user->save();
echo $user->created_at;  // выведет метку времени на момент сохранения записи

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

php
$user->touch('login_time');

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

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

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

Сравнение с трейтами <span id="comparison-with-traits"></span>

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

Плюсы поведений <span id="pros-for-behaviors"></span>

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

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

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

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

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

Плюсы трейтов <span id="pros-for-traits"></span>

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

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