concept-behaviors.md 11.1 KB
Newer Older
1 2 3
Behaviors
=========

Larry Ullman committed
4
Behaviors are instances of [[yii\base\Behavior]], or of a child class. Behaviors, also known
5
as [mixins](http://en.wikipedia.org/wiki/Mixin), allow you to enhance the functionality
Larry Ullman committed
6 7 8
of an existing [[yii\base\Component|component]] class without needing to change the class's inheritance.
Attaching a behavior to a component "injects" the behavior's methods and properties into the component, making those methods and properties accessible as if they were defined in the component class itself. Moreover, a behavior
can respond to the [events](concept-events.md) triggered by the component, which allows behaviors to also customize the normal
9
code execution of the component.
10

11

Larry Ullman committed
12 13
Defining Behaviors <a name="defining-behaviors"></a>
------------------
14

15
To define a behavior, create a class that extends [[yii\base\Behavior]], or extends a child class. For example:
16

Larry Ullman committed
17 18
```php
namespace app\components;
19

Larry Ullman committed
20
use yii\base\Behavior;
21

Larry Ullman committed
22 23 24
class MyBehavior extends Behavior
{
    public $prop1;
Qiang Xue committed
25

Larry Ullman committed
26
    private $_prop2;
27

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

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

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

45 46
The above code defines the behavior class `app\components\MyBehavior`, with two properties--
`prop1` and `prop2`--and one method `foo()`. Note that property `prop2`
47
is defined via the getter `getProp2()` and the setter `setProp2()`. This is the case because [[yii\base\Behavior]] extends [[yii\base\Object]] and therefore supports defining [properties](concept-properties.md) via getters and setters.
48

49 50 51
Because this class is a behavior, when it is attached to a component, that component will then also have the the `prop1` and `prop2` properties and the `foo()` method.

> Tip: Within a behavior, you can access the component that the behavior is attached to through the [[yii\base\Behavior::owner]] property.
Qiang Xue committed
52

53 54 55
Handling Component Events
------------------

Larry Ullman committed
56
If a behavior needs to respond to the events triggered by the component it is attached to, it should override the
57
[[yii\base\Behavior::events()]] method. For example:
Qiang Xue committed
58 59

```php
Larry Ullman committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
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)
    {
        // ...
    }
}
Qiang Xue committed
81 82
```

Larry Ullman committed
83
The [[yii\base\Behavior::events()|events()]] method should return a list of events and their corresponding handlers.
84 85
The above example declares that the [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] event exists and defines
its handler, `beforeValidate()`. When specifying an event handler, you may use one of the following formats:
Larry Ullman committed
86

87 88 89
* a string that refers to the name of a method of the behavior class, like the example above
* an array of an object or class name, and a method name as a string (without parentheses), e.g., `[$object, 'methodName']`;
* an anonymous function
Larry Ullman committed
90 91 92

The signature of an event handler should be as follows, where `$event` refers to the event parameter. Please refer
to the [Events](concept-events.md) section for more details about events.
Qiang Xue committed
93 94

```php
Larry Ullman committed
95 96
function ($event) {
}
97 98
```

Qiang Xue committed
99
Attaching Behaviors <a name="attaching-behaviors"></a>
100 101
-------------------

102
You can attach a behavior to a [[yii\base\Component|component]] either statically or dynamically. The former is more common in practice.
103 104

To attach a behavior statically, override the [[yii\base\Component::behaviors()|behaviors()]] method of the component
105 106
class to which the behavior is being attached. The [[yii\base\Component::behaviors()|behaviors()]] method should return a list of behavior [configurations](concept-configurations.md).
Each behavior configuration can be either a behavior class name or a configuration array:
107 108 109 110 111 112

```php
namespace app\models;

use yii\db\ActiveRecord;
use app\components\MyBehavior;
Qiang Xue committed
113 114 115

class User extends ActiveRecord
{
116 117 118
    public function behaviors()
    {
        return [
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
            // anonymous behavior, behavior class name only
            MyBehavior::className(),

            // named behavior, behavior class name only
            'myBehavior2' => MyBehavior::className(),

            // anonymous behavior, configuration array
            [
                'class' => MyBehavior::className(),
                'prop1' => 'value1',
                'prop2' => 'value2',
            ],

            // named behavior, configuration array
            'myBehavior4' => [
                'class' => MyBehavior::className(),
                'prop1' => 'value1',
                'prop2' => 'value2',
            ]
138 139
        ];
    }
Qiang Xue committed
140 141
}
```
142

143
You may associate a name with a behavior by specifying the array key corresponding to the behavior configuration. In this case, the behavior is called a *named behavior*. In the above example, there are two named behaviors:
144
`myBehavior2` and `myBehavior4`. If a behavior is not associated with a name, it is called an *anonymous behavior*.
145

146

147
To attach a behavior dynamically, call the [[yii\base\Component::attachBehavior()]] method of the component to which the behavior is being attached:
148 149

```php
150 151 152 153 154 155 156 157 158 159 160 161 162 163
use app\components\MyBehavior;

// attach a behavior object
$component->attachBehavior('myBehavior1', new MyBehavior);

// attach a behavior class
$component->attachBehavior('myBehavior2', MyBehavior::className());

// attach a configuration array
$component->attachBehavior('myBehavior3', [
    'class' => MyBehavior::className(),
    'prop1' => 'value1',
    'prop2' => 'value2',
]);
164 165
```

166
You may attach multiple behaviors at once using the [[yii\base\Component::attachBehaviors()]] method:
167 168 169 170 171 172 173 174

```php
$component->attachBehaviors([
    'myBehavior1' => new MyBehavior,  // a named behavior
    MyBehavior::className(),          // an anonymous behavior
]);
```

175
You may also attach behaviors through [configurations](concept-configurations.md) like the following: 
176 177 178 179 180 181 182 183 184 185 186 187

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

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

189 190 191
For more details,
please refer to the [Configurations](concept-configurations.md#configuration-format) section.

Larry Ullman committed
192 193
Using Behaviors <a name="using-behaviors"></a>
---------------
194

Larry Ullman committed
195
To use a behavior, first attach it to a [[yii\base\Component|component]] per the instructions above. Once a behavior is attached to a component, its usage is straightforward.
Larry Ullman committed
196 197

You can access a *public* member variable or a [property](concept-properties.md) defined by a getter and/or a setter
Larry Ullman committed
198
of the behavior through the component it is attached to:
199 200

```php
Larry Ullman committed
201 202 203
// "prop1" is a property defined in the behavior class
echo $component->prop1;
$component->prop1 = $value;
204 205
```

Larry Ullman committed
206
You can also call a *public* method of the behavior similarly:
207

208
```php
Larry Ullman committed
209 210
// foo() is a public method defined in the behavior class
$component->foo();
211
```
212

213
As you can see, although `$component` does not define `prop1` and `foo()`, they can be used as if they are part
Larry Ullman committed
214
of the component definition due to the attached behavior.
215

Larry Ullman committed
216
If two behaviors define the same property or method and they are both attached to the same component,
Larry Ullman committed
217
the behavior that is attached to the component *first* will take precedence when the property or method is accessed.
218

Larry Ullman committed
219
A behavior may be associated with a name when it is attached to a component. If this is the case, you may
Larry Ullman committed
220
access the behavior object using the name:
221 222

```php
Larry Ullman committed
223
$behavior = $component->getBehavior('myBehavior');
224 225
```

Larry Ullman committed
226
You may also get all behaviors attached to a component:
227 228

```php
Larry Ullman committed
229 230
$behaviors = $component->getBehaviors();
```
231 232


Larry Ullman committed
233 234
Detaching Behaviors <a name="detaching-behaviors"></a>
-------------------
235

Larry Ullman committed
236
To detach a behavior, call [[yii\base\Component::detachBehavior()]] with the name associated with the behavior:
237

Larry Ullman committed
238 239 240
```php
$component->detachBehavior('myBehavior1');
```
241

Larry Ullman committed
242
You may also detach *all* behaviors:
243 244

```php
Larry Ullman committed
245
$component->detachBehaviors();
246 247 248
```


Qiang Xue committed
249
Using `TimestampBehavior` <a name="using-timestamp-behavior"></a>
250 251
-------------------------

252
To wrap up, let's take a look at [[yii\behaviors\TimestampBehavior]]. This behavior supports automatically
Larry Ullman committed
253
updating the timestamp attributes of an [[yii\db\ActiveRecord|Active Record]] model anytime the model is saved (e.g., on insert or update).
254

Larry Ullman committed
255
First, attach this behavior to the [[yii\db\ActiveRecord|Active Record]] class that you plan to use:
256 257 258

```php
namespace app\models\User;
259 260

use yii\db\ActiveRecord;
261
use yii\behaviors\TimestampBehavior;
262

263 264
class User extends ActiveRecord
{
265 266 267 268 269
    // ...

    public function behaviors()
    {
        return [
270 271 272 273 274 275
            [
                'class' => TimestampBehavior::className(),
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
276 277 278
            ],
        ];
    }
279 280 281
}
```

Larry Ullman committed
282
The behavior configuration above specifies that when the record is being:
283

Larry Ullman committed
284 285 286
* inserted, the behavior should assign the current timestamp to
  the `created_at` and `updated_at` attributes
* updated, the behavior should assign the current timestamp to the `updated_at` attribute
287

Larry Ullman committed
288
With that code in place, if you have a `User` object and try to save it, you will find its `created_at` and `updated_at` are automatically
289
filled with the current timestamp:
290 291

```php
292 293 294 295 296
$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at;  // shows the current timestamp
```
297

298
The [[yii\behaviors\TimestampBehavior|TimestampBehavior]] also offers a useful method
Larry Ullman committed
299
[[yii\behaviors\TimestampBehavior::touch()|touch()]], which will assign the current timestamp
300
to a specified attribute and save it to the database:
301

302 303 304
```php
$user->touch('login_time');
```
305

Larry Ullman committed
306
Comparing Behaviors with Traits <a name="comparison-with-traits"></a>
307 308 309 310
----------------------

While behaviors are similar to [traits](http://www.php.net/traits) in that they both "inject" their
properties and methods to the primary class, they differ in many aspects. As explained below, they
Larry Ullman committed
311
both have pros and cons. They are more like complements to each other rather than alternatives.
312 313


Larry Ullman committed
314
### Reasons to Use Behaviors <a name="pros-for-behaviors"></a>
315 316 317 318

Behavior classes, like normal classes, support inheritance. Traits, on the other hand,
can be considered as language-supported copy and paste. They do not support inheritance.

Larry Ullman committed
319
Behaviors can be attached and detached to a component dynamically without requiring modification of the component class. To use a trait, you must modify the class using it.
320 321 322 323 324

Behaviors are configurable while traits are not.

Behaviors can customize the code execution of a component by responding to its events.

Larry Ullman committed
325 326
When there can be name conflicts among different behaviors attached to the same component, the conflicts are
automatically resolved by prioritizing the behavior attached to the component first.
327
Name conflicts caused by different traits requires manual resolution by renaming the affected
328 329 330
properties or methods.


Larry Ullman committed
331
### Reasons to Use Traits <a name="pros-for-traits"></a>
332

Larry Ullman committed
333
Traits are much more efficient than behaviors as behaviors are objects that take both time and memory.
334

335
IDEs are more friendly to traits as they are a native language construct.
336