|
|
|
Поведения
|
|
|
|
=========
|
|
|
|
|
|
|
|
Поведения (behaviors) — это экземпляры класса [[yii\base\Behavior]] или класса, унаследованного от него. Поведения,
|
|
|
|
также известные как [примеси](http://ru.wikipedia.org/wiki/Примесь_(программирование)), позволяют расширять
|
|
|
|
функциональность существующих [[yii\base\Component|компонентов]] без необходимости изменения дерева наследования.
|
|
|
|
После прикрепления поведения к компоненту, его методы и свойства "внедряются" в компонент, и становятся доступными
|
|
|
|
так же, как если бы они были объявлены в самом классе компонента. Кроме того, поведение может реагировать на
|
|
|
|
[события](concept-events.md), создаваемые компонентом, что позволяет тонко настраивать или модифицировать
|
|
|
|
обычное выполнение кода компонента.
|
|
|
|
|
|
|
|
|
|
|
|
Создание поведений <a name="defining-behaviors"></a>
|
|
|
|
----------------------------------------------
|
|
|
|
|
|
|
|
Поведения создаются путем расширения базового класса [[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\Object]], который предоставляет возможность определения [свойств](concept-properties.md) через геттеры и сеттеры.
|
|
|
|
|
|
|
|
Так как этот класс является поведением, когда он прикреплён к компоненту, компоненту будут также доступны свойства `prop1`
|
|
|
|
и `prop2`, а также метод `foo()`.
|
|
|
|
|
|
|
|
> Подсказка: Внутри поведения возможно обращаться к компоненту, к которому оно прикреплено, используя свойство
|
|
|
|
[[yii\base\Behavior::owner]].
|
|
|
|
|
|
|
|
|
|
|
|
Обработка событий компонента
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
Если поведению требуется реагировать на события компонента, к которому оно прикреплено, то необходимо переопределить
|
|
|
|
метод [[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) {
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Прикрепление поведений <a name="attaching-behaviors"></a>
|
|
|
|
---------------------------------------------------
|
|
|
|
|
|
|
|
Прикрепить поведение к [[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::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()]]
|
|
|
|
требуемого компонента:
|
|
|
|
|
|
|
|
```php
|
|
|
|
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()]] позволяет прикрепить несколько поведение за раз.
|
|
|
|
Например,
|
|
|
|
|
|
|
|
```php
|
|
|
|
$component->attachBehaviors([
|
|
|
|
'myBehavior1' => new MyBehavior, // именованное поведение
|
|
|
|
MyBehavior::className(), // анонимное поведение
|
|
|
|
]);
|
|
|
|
```
|
|
|
|
|
|
|
|
Так же, прикрепить поведение к компоненту можно через [конфигурацию](concept-configurations.md), как показано ниже:
|
|
|
|
|
|
|
|
```php
|
|
|
|
[
|
|
|
|
'as myBehavior2' => MyBehavior::className(),
|
|
|
|
|
|
|
|
'as myBehavior3' => [
|
|
|
|
'class' => MyBehavior::className(),
|
|
|
|
'prop1' => 'value1',
|
|
|
|
'prop2' => 'value2',
|
|
|
|
],
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
Более детальная информация приведена в разделе [Конфигурации](concept-configurations.md#configuration-format).
|
|
|
|
|
|
|
|
Использование поведений <a name="using-behaviors"></a>
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
Для использования поведения, его необходимо прикрепить к [[yii\base\Component|компоненту]] как описано выше. После того,
|
|
|
|
как поведение прикреплено к компоненту, его использование не вызывает сложностей.
|
|
|
|
|
|
|
|
Вы можете обращаться к *публичным* переменным или [свойствам](concept-properties.md), объявленным с использованием
|
|
|
|
геттеров и сеттеров в поведении, через компонент, к которому оно прикреплено:
|
|
|
|
|
|
|
|
```php
|
|
|
|
// публичное свойство "prop1" объявленное в классе поведения
|
|
|
|
echo $component->prop1;
|
|
|
|
$component->prop1 = $value;
|
|
|
|
```
|
|
|
|
|
|
|
|
Аналогично, вы можете вызывать *публичные* методы поведения,
|
|
|
|
|
|
|
|
```php
|
|
|
|
// публичный метод bar() объявленный в классе поведения
|
|
|
|
$component->bar();
|
|
|
|
```
|
|
|
|
|
|
|
|
Обратите внимание, хотя `$component` не имеет свойства `prop1` и метода `foo()`, они могут быть использованы,
|
|
|
|
как будто являются членами этого класса.
|
|
|
|
|
|
|
|
В случае, когда два поведения, имеющие свойства или методы с одинаковыми именами, прикреплены к одному компоненту,
|
|
|
|
преимущество будет у поведения, прикрепленного раньше.
|
|
|
|
|
|
|
|
Если при прикреплении поведения к компоненту указано имя, можно обращаться к поведению по этому имени, как показано ниже:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$behavior = $component->getBehavior('myBehavior');
|
|
|
|
```
|
|
|
|
|
|
|
|
Также можно получить все поведения, прикрепленные к компоненту:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$behaviors = $component->getBehaviors();
|
|
|
|
```
|
|
|
|
|
|
|
|
Отвязывание поведений<a name="detaching-behaviors"></a>
|
|
|
|
-------------------------------------------------
|
|
|
|
|
|
|
|
Чтобы отвязать поведение от компонента, необходимо вызвать метод [[yii\base\Component::detachBehavior()]], указав имя,
|
|
|
|
связанное с поведением:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$component->detachBehavior('myBehavior1');
|
|
|
|
```
|
|
|
|
|
|
|
|
Так же, возможно отвязать *все* поведения:
|
|
|
|
|
|
|
|
```php
|
|
|
|
$component->detachBehaviors();
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Использование поведения `TimestampBehavior` <a name="using-timestamp-behavior"></a>
|
|
|
|
--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
В заключении, давайте посмотрим на [[yii\behaviors\TimestampBehavior]] — поведение, которое позволяет автоматически
|
|
|
|
обновлять атрибуты с метками времени при сохранении [[yii\db\ActiveRecord|Active Record]] моделей.
|
|
|
|
|
|
|
|
Для начала, необходимо прикрепить поведение к классу [[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::className(),
|
|
|
|
'attributes' => [
|
|
|
|
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
|
|
|
|
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Конфигурация выше описывает следующее:
|
|
|
|
|
|
|
|
* при вставке новой записи поведение должно присвоить текущую метку времени атрибутам `created_at` и `updated_at`;
|
|
|
|
* при обновлении существующей записи поведение должно присвоить текущую метку времени атрибуту `updated_at`.
|
|
|
|
|
|
|
|
Теперь, если сохранить объект `User`, то в его атрибуты `created_at` и `updated_at` будут автоматически установлены
|
|
|
|
значения метки времени на момент сохранения записи:
|
|
|
|
|
|
|
|
```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');
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Сравнение с трейтами <a name="comparison-with-traits"></a>
|
|
|
|
---------------------------------------------------
|
|
|
|
|
|
|
|
Несмотря на то, что поведения схожи с [трейтами](http://ru2.php.net/manual/ru/language.oop5.traits.php) тем, что
|
|
|
|
"внедряют" свои свойства и методы в основной класс, они имеют множество отличий. Они оба имеют свои плюсы и минусы,
|
|
|
|
и, скорее, дополняют друг друга, а не заменяют.
|
|
|
|
|
|
|
|
|
|
|
|
### Плюсы поведений <a name="pros-for-behaviors"></a>
|
|
|
|
|
|
|
|
Поведения, как и любые другие классы, поддерживают наследование. Трейты же можно рассматривать как копипейст
|
|
|
|
на уровне языка. Они наследование не поддерживают.
|
|
|
|
|
|
|
|
Поведения могут быть прикреплены и отвязаны от компонента динамически, без необходимости модифицирования класса
|
|
|
|
компонента. Для использование трейтов необходимо модифицировать класс.
|
|
|
|
|
|
|
|
Поведения, в отличие от трейтов, можно настраивать.
|
|
|
|
|
|
|
|
Поведения можно настраивать таким образом, чтобы они реагировали на события компонента.
|
|
|
|
|
|
|
|
Конфликты имен свойств и методов поведений, прикрепленных к компоненту, разрешаются на основе порядка их подключения.
|
|
|
|
Конфликты имен, вызванные различными трейтами, требуют ручного переименования конфликтующих свойств или методов.
|
|
|
|
|
|
|
|
|
|
|
|
### Плюсы трейтов <a name="pros-for-traits"></a>
|
|
|
|
|
|
|
|
Трейты являются гораздо более производительными, чем поведения, которые, являясь объектами, требуют
|
|
|
|
дополнительного времени и памяти.
|
|
|
|
|
|
|
|
Многие IDE поддерживают работу с трейтами, так как они являются стандартными конструкциями языка.
|
|
|
|
|