concept-behaviors.md 16.8 KB
Newer Older
1 2 3 4 5 6 7 8
Поведения
=========

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


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

Для использования поведения, его необходимо прикрепить к [[yii\base\Component|компоненту]]. Подробнее о том, как 
17
это сделать, описано в следующем разделе.
18 19 20

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

21
Вы можете обращаться к *публичным* переменным или [свойствам](concept-properties.md), объявленным с использованием 
22 23 24
геттеров и сеттеров в поведении, через компонент, к которому оно прикреплено, как показано ниже,

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

30
Аналогично, вы можете вызывать *публичные* методы поведения,
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

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

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

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

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

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

50
Также можно получить все поведения, прикрепленные к компоненту:
51 52 53 54 55 56 57 58 59 60 61


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


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

Прикрепить поведение к [[yii\base\Component|компоненту]] можно статически (statically) или динамически (dynamically). 
62
На практике, чаще используется статическое прикрепление.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106

Для того чтобы прикрепить поведение статически, необходимо переопределить метод 
[[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) поведений. Конфигурация поведения представляет собой имя класса поведения,
либо массив его настроек.

Вы можете связать имя с поведением, указав его в качестве ключа элемента массива, соответствующего конфигурации
107 108
поведения. В таком случае, поведение называется *именованным поведением (named behavior)*. В примере выше, 
два именованных поведения: `myBehavior2` и `myBehavior4`. Если с поведением не связано имя, такое поведение называется 
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
*анонимным поведением (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), как показано ниже.
142
Более детальная информация приведена в разделе [Конфигурации](concept-configurations.md#configuration-format).
143 144 145 146 147 148 149 150 151 152 153 154 155 156

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

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


157
Отвязывание Поведений<a name="detaching-behaviors"></a>
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
-------------------

Чтобы отвязать (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]].

216
Если поведению требуется реагировать на события компонента, к которому оно прикреплено, то необходимо переопределить 
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
метод [[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)
    {
        // ...
    }
}
```
242

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

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

251 252
Сигнатура (signature) функции обработчика события должна выглядеть как показано ниже, где `$event` содержит параметр 
события. Более детальная информация приведена в разделе [События](concept-events.md).
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

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

300 301
Теперь, если сохранить объект `User`, то в его атрибуты `created_at` и `updated_at` будут автоматически установлены
значения метки времени на момент сохранения записи:
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

```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>

329 330
Поведения, как и любые другие классы, поддерживают наследование. Трейты можно рассматривать как копипейст 
на уровне языка. Они не поддерживают наследование.
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349

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

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

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

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


### Плюсы Трейтов <a name="pros-for-traits"></a>

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

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