concept-behaviors.md 11.2 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 21
use yii\base\Model;
use yii\base\Behavior;
22

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

Larry Ullman committed
27
    private $_prop2;
28

Larry Ullman committed
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
The above code defines the behavior class `app\components\MyBehavior`, with two properties--
`prop1` and `prop2`--and one method `foo()`. Note that property `prop2`
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.
49

50 51 52
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
53

54 55 56
Handling Component Events
------------------

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

```php
Larry Ullman committed
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
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
82 83
```

Larry Ullman committed
84
The [[yii\base\Behavior::events()|events()]] method should return a list of events and their corresponding handlers.
85 86
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
87

88 89 90
* 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
91 92 93

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
94 95

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

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

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

To attach a behavior statically, override the [[yii\base\Component::behaviors()|behaviors()]] method of the component
106 107
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:
108 109 110 111 112 113

```php
namespace app\models;

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

class User extends ActiveRecord
{
117 118 119
    public function behaviors()
    {
        return [
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
            // 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',
            ]
139 140
        ];
    }
Qiang Xue committed
141 142
}
```
143

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

147

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

```php
151 152 153 154 155 156 157 158 159 160 161 162 163 164
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',
]);
165 166
```

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

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

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

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

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

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

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

Larry Ullman committed
196
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
197 198

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

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

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

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

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

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

Larry Ullman committed
220
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
221
access the behavior object using the name:
222 223

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

Larry Ullman committed
285 286 287
* 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
288

Larry Ullman committed
289
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
290
filled with the current timestamp:
291 292

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

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

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

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

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


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

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
320
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.
321 322 323 324 325

Behaviors are configurable while traits are not.

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

Larry Ullman committed
326 327 328
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.
Name conflicts caused by different traits requires manually resolution by renaming the affected
329 330 331
properties or methods.


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

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

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