Поведения ========= Поведения (behaviors) — это экземпляры класса [[yii\base\Behavior]] или класса, унаследованного от него. Поведения, также известные как [примеси](http://ru.wikipedia.org/wiki/Примесь_(программирование)), позволяют расширять функциональность существующих [[yii\base\Component|компонентов]] без необходимости изменения дерева наследования. После прикрепления поведения к компоненту, его методы и свойства "внедряются" в компонент, и становятся доступными так же, как если бы они были объявлены в самом классе компонента. Кроме того, поведение может реагировать на [события](concept-events.md), создаваемые компонентом, что позволяет тонко настраивать или модифицировать обычное выполнение кода компонента. Создание поведений ---------------------------------------------- Поведения создаются путем расширения базового класса [[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` содержащий 2 свойства `prop1` и `prop2`, и один метод `foo()`. Обратите внимание, свойство `prop2` объявлено с использованием геттера `getProp2()` и сеттера `setProp2()`. Это возможно, так как [[yii\base\Behavior]] является дочерним классом для [[yii\base\BaseObject]], который предоставляет возможность определения [свойств](concept-properties.md) через геттеры и сеттеры. Так как этот класс является поведением, когда он прикреплён к компоненту, компоненту будут также доступны свойства `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` содержит параметр события. Более детальная информация приведена в разделе [События](concept-events.md). ```php function ($event) { } ``` Прикрепление поведений --------------------------------------------------- Прикрепить поведение к [[yii\base\Component|компоненту]] можно как статически, так и динамически. На практике чаще используется статическое прикрепление. Для того чтобы прикрепить поведение статически, необходимо переопределить метод [[yii\base\Component::behaviors()|behaviors()]] компонента, к которому его планируется прикрепить. Метод [[yii\base\Component::behaviors()|behaviors()]] должен возвращать список [конфигураций](concept-configurations.md) поведений. Конфигурация поведения представляет собой имя класса поведения, либо массив его настроек: ```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, // анонимное поведение ]); ``` Так же, прикрепить поведение к компоненту можно через [конфигурацию](concept-configurations.md), как показано ниже: ```php [ 'as myBehavior2' => MyBehavior::class, 'as myBehavior3' => [ 'class' => MyBehavior::class, 'prop1' => 'value1', 'prop2' => 'value2', ], ] ``` Более детальная информация приведена в разделе [Конфигурации](concept-configurations.md#configuration-format). Использование поведений ------------------------------------------------ Для использования поведения, его необходимо прикрепить к [[yii\base\Component|компоненту]] как описано выше. После того, как поведение прикреплено к компоненту, его использование не вызывает сложностей. Вы можете обращаться к *публичным* переменным или [свойствам](concept-properties.md), объявленным с использованием геттеров и сеттеров в поведении, через компонент, к которому оно прикреплено: ```php // публичное свойство "prop1" объявленное в классе поведения echo $component->prop1; $component->prop1 = $value; ``` Аналогично, вы можете вызывать *публичные* методы поведения, ```php // публичный метод foo() объявленный в классе поведения $component->foo(); ``` Обратите внимание, хотя `$component` не имеет свойства `prop1` и метода `foo()`, они могут быть использованы, как будто являются членами этого класса. В случае, когда два поведения, имеющие свойства или методы с одинаковыми именами, прикреплены к одному компоненту, преимущество будет у поведения, прикрепленного раньше. Если при прикреплении поведения к компоненту указано имя, можно обращаться к поведению по этому имени, как показано ниже: ```php $behavior = $component->getBehavior('myBehavior'); ``` Также можно получить все поведения, прикрепленные к компоненту: ```php $behaviors = $component->getBehaviors(); ``` Отвязывание поведений ------------------------------------------------- Чтобы отвязать поведение от компонента, необходимо вызвать метод [[yii\base\Component::detachBehavior()]], указав имя, связанное с поведением: ```php $component->detachBehavior('myBehavior1'); ``` Так же, возможно отвязать *все* поведения: ```php $component->detachBehaviors(); ``` Использование поведения `TimestampBehavior` -------------------------------------------------------------------------- В заключении, давайте посмотрим на [[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 = 'test@example.com'; $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 текстом, получаемым из другого атрибута. - [[yii\behaviors\AttributeBehavior]] - автоматически задаёт указанное значение одному или нескольким атрибутам ActiveRecord при срабатывании определённых событий. - [yii2tech\ar\softdelete\SoftDeleteBehavior](https://github.com/yii2tech/ar-softdelete) - предоставляет методы для «мягкого» удаления и воосстановления ActiveRecord. То есть выставляет статус или флаг, который показывает, что запись удалена. - [yii2tech\ar\position\PositionBehavior](https://github.com/yii2tech/ar-position) - позволяет упралять порядком записей через специальные методы. Информация сохраняется в целочисленном поле. Сравнение с трейтами --------------------------------------------------- Несмотря на то, что поведения схожи с [трейтами](http://ru2.php.net/manual/ru/language.oop5.traits.php) тем, что "внедряют" свои свойства и методы в основной класс, они имеют множество отличий. Они оба имеют свои плюсы и минусы, и, скорее, дополняют друг друга, а не заменяют. ### Плюсы поведений Поведения, как и любые другие классы, поддерживают наследование. Трейты же можно рассматривать как копипейст на уровне языка. Они наследование не поддерживают. Поведения могут быть прикреплены и отвязаны от компонента динамически, без необходимости модифицирования класса компонента. Для использования трейтов необходимо модифицировать класс. Поведения, в отличие от трейтов, можно настраивать. Поведения можно настраивать таким образом, чтобы они реагировали на события компонента. Конфликты имен свойств и методов поведений, прикрепленных к компоненту, разрешаются на основе порядка их подключения. Конфликты имен, вызванные различными трейтами, требуют ручного переименования конфликтующих свойств или методов. ### Плюсы трейтов Трейты являются гораздо более производительными, чем поведения, которые, являясь объектами, требуют дополнительного времени и памяти. Многие IDE поддерживают работу с трейтами, так как они являются стандартными конструкциями языка.