Поведения ¶
Поведения (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 поддерживают работу с трейтами, так как они являются стандартными конструкциями языка.