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

4 5 6 7 8
Behaviors are instances of [[yii\base\Behavior]] or its child class. Behaviors, also known
as [mixins](http://en.wikipedia.org/wiki/Mixin), allow you to enhance the functionality
of an existing [[yii\base\Component|component]] class without the need of changing its class inheritance.
When a behavior is attached to a component, it will "inject" its methods and properties into the component,
and you can access these methods and properties as if they are defined by the component class. Moreover, a behavior
9
can respond to the [events](concept-events.md) triggered by the component so that it can customize or adapt the normal
10
code execution of the component.
11

12

Qiang Xue committed
13
Using Behaviors <a name="using-behaviors"></a>
14 15
---------------

16
To use a behavior, you first need to attach it to a [[yii\base\Component|component]]. We will describe how to
17
attach a behavior in the next subsection.
18

19
Once a behavior is attached to a component, its usage is straightforward.
20

21
You can access a *public* member variable or a [property](concept-properties.md) defined by a getter and/or a setter
22
of the behavior through the component it is attached to, like the following,
23 24

```php
25 26 27 28
// "prop1" is a property defined in the behavior class
echo $component->prop1;
$component->prop1 = $value;
```
Qiang Xue committed
29

30
You can also call a *public* method of the behavior similarly,
31

32 33 34
```php
// bar() is a public method defined in the behavior class
$component->bar();
35 36
```

37 38 39 40 41
As you can see, although `$component` does not define `prop1` and `bar()`, they can be used as if they are part
of the component definition.

If two behaviors define the same property or method and they are both attached to the same component,
the behavior that is attached to the component first will take precedence when the property or method is being accessed.
Qiang Xue committed
42

43 44
A behavior may be associated with a name when it is attached to a component. If this is the case, you may
access the behavior object using the name, like the following,
Qiang Xue committed
45 46

```php
47
$behavior = $component->getBehavior('myBehavior');
Qiang Xue committed
48 49
```

50
You may also get all behaviors attached to a component:
Qiang Xue committed
51 52

```php
53 54 55 56
$behaviors = $component->getBehaviors();
```


Qiang Xue committed
57
Attaching Behaviors <a name="attaching-behaviors"></a>
58 59 60 61 62 63 64 65 66 67 68 69 70
-------------------

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

To attach a behavior statically, override the [[yii\base\Component::behaviors()|behaviors()]] method of the component
class that it is being attached. For example,

```php
namespace app\models;

use yii\db\ActiveRecord;
use app\components\MyBehavior;
Qiang Xue committed
71 72 73

class User extends ActiveRecord
{
74 75 76
    public function behaviors()
    {
        return [
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
            // 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',
            ]
96 97
        ];
    }
Qiang Xue committed
98 99
}
```
100

Qiang Xue committed
101
The [[yii\base\Component::behaviors()|behaviors()]] method should return a list of behavior [configurations](concept-configurations.md).
102 103 104 105 106
Each behavior configuration can be either a behavior class name or a configuration array.

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:
`myBehavior2` and `myBehavior4`. If a behavior is not associated with a name, it is called an *anonymous behavior*.
107

108 109 110

To attach a behavior dynamically, call the [[yii\base\Component::attachBehavior()]] method of the component
that it is attached to. For example,
111 112

```php
113 114 115 116 117 118 119 120 121 122 123 124 125 126
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',
]);
127 128
```

129 130 131 132 133 134 135 136 137 138
You may attach multiple behaviors at once by using the [[yii\base\Component::attachBehaviors()]] method.
For example,

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

139 140 141 142 143 144 145 146 147 148 149 150 151 152
You may also attach behaviors through [configurations](concept-configurations.md) like the following. For more details,
please refer to the [Configurations](concept-configurations.md#configuration-format) section.

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

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

154

Qiang Xue committed
155
Detaching Behaviors <a name="detaching-behaviors"></a>
156 157 158
-------------------

To detach a behavior, you can call [[yii\base\Component::detachBehavior()]] with the name associated with the behavior:
159 160

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

164
You may also detach *all* behaviors:
165

166 167 168
```php
$component->detachBehaviors();
```
169 170


Qiang Xue committed
171
Defining Behaviors <a name="defining-behaviors"></a>
172 173 174
------------------

To define a behavior, create a class by extending from [[yii\base\Behavior]] or its child class. For example,
175 176

```php
177 178
namespace app\components;

179
use yii\base\Model;
180 181 182
use yii\base\Behavior;

class MyBehavior extends Behavior
183
{
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    public $prop1;

    private $_prop2;

    public function getProp2()
    {
        return $this->_prop2;
    }

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

    public function foo()
    {
        // ...
    }
202 203 204
}
```

tsvetann committed
205
The above code defines the behavior class `app\components\MyBehavior` which will provide two properties
206 207
`prop1` and `prop2`, and one method `foo()` to the component it is attached to. Note that property `prop2`
is defined via the getter `getProp2()` and the setter `setProp2()`. This is so because [[yii\base\Object]]
208
is an ancestor class of [[yii\base\Behavior]], which supports defining [properties](concept-properties.md) by getters/setters.
209 210 211 212 213

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

If a behavior needs to respond to the events triggered by the component it is attached to, it should override the
[[yii\base\Behavior::events()]] method. For example,
214 215

```php
216 217
namespace app\components;

218
use yii\db\ActiveRecord;
219 220 221
use yii\base\Behavior;

class MyBehavior extends Behavior
222
{
223 224 225 226 227 228 229 230 231 232 233 234 235
    // ...

    public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
        ];
    }

    public function beforeValidate($event)
    {
        // ...
    }
236 237 238
}
```

239 240 241 242 243 244 245 246 247
The [[yii\base\Behavior::events()|events()]] method should return a list of events and their corresponding handlers.
The above example declares that the [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] event and
its handler `beforeValidate()`. When specifying an event handler, you may use one of the following formats:

* 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, e.g., `[$object, 'methodName']`;
* an anonymous function.

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

```php
251 252 253 254 255
function ($event) {
}
```


Qiang Xue committed
256
Using `TimestampBehavior` <a name="using-timestamp-behavior"></a>
257 258 259 260 261 262 263 264 265
-------------------------

To wrap up, let's take a look at [[yii\behaviors\TimestampBehavior]] - a behavior that supports automatically
updating the timestamp attributes of an [[yii\db\ActiveRecord|Active Record]] when it is being saved.

First, attach this behavior to the [[yii\db\ActiveRecord|Active Record]] class that you plan to use.

```php
namespace app\models\User;
266 267

use yii\db\ActiveRecord;
268
use yii\behaviors\TimestampBehavior;
269

270 271
class User extends ActiveRecord
{
272 273 274 275 276
    // ...

    public function behaviors()
    {
        return [
277 278 279 280 281 282
            [
                'class' => TimestampBehavior::className(),
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
283 284 285
            ],
        ];
    }
286 287 288
}
```

289 290 291 292 293 294 295 296
The behavior configuration above specifies that

* when the record is being inserted, the behavior should assign the current timestamp to
  the `created_at` and `updated_at` attributes;
* when the record is being updated, the behavior should assign the current timestamp to the `updated_at` attribute.

Now if you have a `User` object and try to save it, you will find its `created_at` and `updated_at` are automatically
filled with the current timestamp:
297 298

```php
299 300 301 302 303
$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at;  // shows the current timestamp
```
304

305 306 307
The [[yii\behaviors\TimestampBehavior|TimestampBehavior]] also offers a useful method
[[yii\behaviors\TimestampBehavior::touch()|touch()]] which will assign the current timestamp
to a specified attribute and save it to the database:
308

309 310 311
```php
$user->touch('login_time');
```
312 313


Qiang Xue committed
314
Comparison with Traits <a name="comparison-with-traits"></a>
315 316 317 318 319 320 321
----------------------

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
both have pros and cons. They are more like complements rather than replacements to each other.


322
<a name="pros-for-behaviors"></a>
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
### Pros for Behaviors

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.

Behaviors can be attached and detached to a component dynamically without requiring you to modify the component class.
To use a trait, you must modify the class using it.

Behaviors are configurable while traits are not.

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

When there is name conflict among different behaviors attached to the same component, the conflict is
automatically resolved by respecting the behavior that is attached to the component first.
Name conflict caused by different traits requires you to manually resolve it by renaming the affected
properties or methods.


Qiang Xue committed
341
### Pros for Traits <a name="pros-for-traits"></a>
342 343 344 345

Traits are much more efficient than behaviors because behaviors are objects which take both time and memory.

IDEs are more friendly to traits as they are language construct.
346