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

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


13
Создание поведений <span id="defining-behaviors"></span>
14
----------------------------------------------
15

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

18 19
```php
namespace app\components;
20

21
use yii\base\Behavior;
22

23 24 25
class MyBehavior extends Behavior
{
    public $prop1;
26

27
    private $_prop2;
28

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
    public function getProp2()
    {
        return $this->_prop2;
    }

    public function setProp2($value)
    {
        $this->_prop2 = $value;
    }

    public function foo()
    {
        // ...
    }
}
44 45
```

46 47 48 49
В приведенном выше примере, объявлен класс поведения `app\components\MyBehavior` содержащий 2 свойства
`prop1` и `prop2`, и один метод `foo()`. Обратите внимание, свойство `prop2` объявлено с использованием геттера
`getProp2()` и сеттера `setProp2()`. Это возможно, так как [[yii\base\Behavior]] является дочерним классом для
[[yii\base\Object]], который предоставляет возможность определения [свойств](concept-properties.md) через геттеры и сеттеры.
50

51 52 53 54 55 56 57 58 59
Так как этот класс является поведением, когда он прикреплён к компоненту, компоненту будут также доступны свойства `prop1`
и `prop2`, а также метод `foo()`.

> Подсказка: Внутри поведения возможно обращаться к компоненту, к которому оно прикреплено, используя свойство
  [[yii\base\Behavior::owner]].


Обработка событий компонента
-------------------------
60

61 62
Если поведению требуется реагировать на события компонента, к которому оно прикреплено, то необходимо переопределить
метод [[yii\base\Behavior::events()]]. Например,
63 64

```php
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
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)
    {
        // ...
    }
}
86 87
```

88 89 90 91 92 93 94
Метод [[yii\base\Behavior::events()|events()]] должен возвращать список событий и соответствующих им обработчиков.
В приведенном выше примере, объявлено событие [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]]
и его обработчик `beforeValidate()`. Указать обработчик события, можно одним из следующих способов:

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

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

```php
100 101
function ($event) {
}
102 103
```

104
Прикрепление поведений <span id="attaching-behaviors"></span>
105
---------------------------------------------------
106

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

110 111 112 113
Для того чтобы прикрепить поведение статически, необходимо переопределить метод [[yii\base\Component::behaviors()|behaviors()]]
компонента, к которому его планируется прикрепить. Метод [[yii\base\Component::behaviors()|behaviors()]] должен возвращать
список [конфигураций](concept-configurations.md) поведений.  Конфигурация поведения представляет собой имя класса поведения,
либо массив его настроек:
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 142 143 144 145 146 147 148 149 150

```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',
            ]
        ];
    }
}
```

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

155 156
Для того, чтобы прикрепить поведение динамически, необходимо вызвать метод [[yii\base\Component::attachBehavior()]]
требуемого компонента:
157 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

```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(),          // анонимное поведение
]);
```

185
Так же, прикрепить поведение к компоненту можно через [конфигурацию](concept-configurations.md), как показано ниже:
186 187 188 189 190 191 192 193 194 195 196 197 198

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

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

199
Более детальная информация приведена в разделе [Конфигурации](concept-configurations.md#configuration-format).
200

201
Использование поведений <span id="using-behaviors"></span>
202
------------------------------------------------
203

204 205 206 207 208
Для использования поведения, его необходимо прикрепить к [[yii\base\Component|компоненту]] как описано выше. После того,
как поведение прикреплено к компоненту, его использование не вызывает сложностей.

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

```php
211 212 213
// публичное свойство "prop1" объявленное в классе поведения
echo $component->prop1;
$component->prop1 = $value;
214 215
```

216
Аналогично, вы можете вызывать *публичные* методы поведения,
217 218

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

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

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

229
Если при прикреплении поведения к компоненту указано имя, можно обращаться к поведению по этому имени, как показано ниже:
230 231

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

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

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

241
Отвязывание поведений<span id="detaching-behaviors"></span>
242
-------------------------------------------------
243

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

247 248
```php
$component->detachBehavior('myBehavior1');
249
```
250

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

```php
254
$component->detachBehaviors();
255 256 257
```


258
Использование поведения `TimestampBehavior` <span id="using-timestamp-behavior"></span>
259
--------------------------------------------------------------------------
260

261
В заключении, давайте посмотрим на [[yii\behaviors\TimestampBehavior]] — поведение, которое позволяет автоматически
262 263
обновлять атрибуты с метками времени при сохранении [[yii\db\ActiveRecord|Active Record]] моделей.

264
Для начала, необходимо прикрепить поведение к классу [[yii\db\ActiveRecord|Active Record]], в котором это необходимо:
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

```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'],
                ],
            ],
        ];
    }
}
```

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

293 294
* при вставке новой записи поведение должно присвоить текущую метку времени атрибутам  `created_at` и `updated_at`;
* при обновлении существующей записи поведение должно присвоить текущую метку времени атрибуту `updated_at`.
295

296 297
Теперь, если сохранить объект `User`, то в его атрибуты `created_at` и `updated_at` будут автоматически установлены
значения метки времени на момент сохранения записи:
298 299 300 301 302

```php
$user = new User;
$user->email = 'test@example.com';
$user->save();
303
echo $user->created_at;  // выведет метку времени на момент сохранения записи
304 305 306 307 308 309 310 311 312 313 314
```

Поведение [[yii\behaviors\TimestampBehavior|TimestampBehavior]] так же содержит полезный метод
[[yii\behaviors\TimestampBehavior::touch()|touch()]], который устанавливает текущую метку времени указанному атрибуту
и сохраняет его в базу данных:

```php
$user->touch('login_time');
```


315
Сравнение с трейтами <span id="comparison-with-traits"></span>
316
---------------------------------------------------
317 318 319 320 321 322

Несмотря на то, что поведения схожи с [трейтами](http://ru2.php.net/manual/ru/language.oop5.traits.php) тем, что 
"внедряют" свои свойства и методы в основной класс, они имеют множество отличий. Они оба имеют свои плюсы и минусы, 
и, скорее, дополняют друг друга, а не заменяют.


323
### Плюсы поведений <span id="pros-for-behaviors"></span>
324

325 326
Поведения, как и любые другие классы, поддерживают наследование. Трейты же можно рассматривать как копипейст
на уровне языка. Они наследование не поддерживают.
327 328

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

331
Поведения, в отличие от трейтов, можно настраивать.
332 333 334 335 336 337 338

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

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


339
### Плюсы трейтов <span id="pros-for-traits"></span>
340

Arthur Khachaturov committed
341
Трейты являются гораздо более производительными, чем поведения, которые, являясь объектами, требуют 
342 343 344 345
дополнительного времени и памяти.

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