Поведения
=========

Поведения (behaviors) - это экземпляры класса [[yii\base\Behavior]] или класса, унаследованного от него. Поведения, 
также известные как [примеси](http://ru.wikipedia.org/wiki/Примесь_(программирование)), позволяют расширять 
функциональность существующих [[yii\base\Component|компонентов]] без необходимости их изменения.
После прикрепления поведения к компоненту, его методы и свойства "внедряются" в компонент, и становятся доступными
так же, как если бы они были объявлены в самом классе компонента. Кроме того, поведение может реагировать на 
[события](concept-events.md), создаваемые компонентом, что позволяет тонко настраивать или модифицировать 
обычное выполнение кода компонента.


Использование Поведений <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` и метода `bar()`, они могут быть использованы, 
как будто являются членами этого класса.

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

Поведение может быть связано (associated) по имени по время прикрепления к компоненту. Это предоставляет возможность, 
обращаться к поведению по его имени, как показано ниже,

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

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


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


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

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

Для того чтобы прикрепить поведение статически, необходимо переопределить метод 
[[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::className(),

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

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

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

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

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

Для того, чтобы прикрепить поведение динамически, необходимо вызвать метод [[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), как показано ниже.
Более детальная информация приведена в разделе [Конфигурации](concept-configurations.md#configuration-format).

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

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


Отвязывание Поведений<a name="detaching-behaviors"></a>
-------------------

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

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

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

```php
$component->detachBehaviors();
```


Создание Поведений <a name="defining-behaviors"></a>
------------------

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

```php
namespace app\components;

use yii\base\Model;
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) через геттеры и сеттеры.

Внутри поведения возможно обращаться к свойствам компонента, к которому оно прикреплено, используя свойство
[[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']`;
* анонимная функция.

Сигнатура (signature) функции обработчика события должна выглядеть как показано ниже, где `$event` содержит параметр 
события. Более детальная информация приведена в разделе [События](concept-events.md).

```php
function ($event) {
}
```


Использование поведения `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'],
                ],
            ],
        ];
    }
}
```

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

* когда будет добавлена новая запись (insert), поведение должно присвоить текущую метку времени (timestamp) атрибутам  
  `created_at` и `updated_at`;
* когда будет обновлена существующая запись (update), поведение должно присвоить текущую метку времени атрибуту 
  `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 поддерживают работу с трейтами, так как они являются стандартными конструкциями языка.