concept-events.md 10.7 KB
Newer Older
1 2 3
Events
======

Qiang Xue committed
4 5
Events allow you to inject custom code into existing code at certain execution points. You can attach custom
code to an event so that when the event is triggered, the code gets executed automatically. For example,
6 7
a mailer object may trigger a `messageSent` event when it successfully sends a message. If you want to keep
track of the messages that are successfully sent, you could then simply attach the tracking code to the `messageSent` event.
8

Qiang Xue committed
9
Yii introduces a base class called [[yii\base\Component]] to support events. If a class needs to trigger
Larry Ullman committed
10
events, it should extend from [[yii\base\Component]], or from a child class.
11

Alexander Makarov committed
12

Qiang Xue committed
13
Event Handlers <a name="event-handlers"></a>
Qiang Xue committed
14 15 16
--------------

An event handler is a [PHP callback](http://www.php.net/manual/en/language.types.callable.php) that gets executed
Larry Ullman committed
17
when the event it is attached to is triggered. You can use any of the following callbacks:
Alexander Makarov committed
18

Larry Ullman committed
19 20 21
- a global PHP function specified as a string (without parentheses), e.g., `'trim'`;
- an object method specified as an array of an object and a method name as a string (without parenthess), e.g., `[$object, 'methodName']`;
- a static class method specified as an array of a class name and a method name as a string (without parentheses), e.g., `[$class, 'methodName']`;
Qiang Xue committed
22
- an anonymous function, e.g., `function ($event) { ... }`.
Alexander Makarov committed
23

Qiang Xue committed
24
The signature of an event handler is:
Alexander Makarov committed
25 26

```php
Qiang Xue committed
27
function ($event) {
Larry Ullman committed
28
    // $event is an object of yii\base\Event or a child class
Qiang Xue committed
29
}
Alexander Makarov committed
30
```
31

Larry Ullman committed
32
Through the `$event` parameter, an event handler may get the following information about the event that occurred:
33

Qiang Xue committed
34
- [[yii\base\Event::name|event name]]
Larry Ullman committed
35 36
- [[yii\base\Event::sender|event sender]]: the object whose `trigger()` method was called
- [[yii\base\Event::data|custom data]]: the data that is provided when attaching the event handler (to be explained next)
Larry Ullman committed
37

Larry Ullman committed
38

Qiang Xue committed
39
Attaching Event Handlers <a name="attaching-event-handlers"></a>
Qiang Xue committed
40 41
------------------------

Larry Ullman committed
42
You can attach a handler to an event by calling the [[yii\base\Component::on()]] method. For example:
Larry Ullman committed
43 44

```php
Qiang Xue committed
45 46
$foo = new Foo;

Larry Ullman committed
47
// this handler is a global function
Qiang Xue committed
48
$foo->on(Foo::EVENT_HELLO, 'function_name');
Larry Ullman committed
49

Larry Ullman committed
50
// this handler is an object method
Qiang Xue committed
51
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
Larry Ullman committed
52

Larry Ullman committed
53
// this handler is a static class method
Qiang Xue committed
54 55
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

Larry Ullman committed
56
// this handler is an anonymous function
Qiang Xue committed
57 58 59
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // event handling logic
});
Larry Ullman committed
60 61
```

62
You may also attach event handlers through [configurations](concept-configurations.md). For more details, please
63
refer to the [Configurations](concept-configurations.md#configuration-format) section.
64 65


Qiang Xue committed
66
When attaching an event handler, you may provide additional data as the third parameter to [[yii\base\Component::on()]].
Larry Ullman committed
67
The data will be made available to the handler when the event is triggered and the handler is called. For example:
Larry Ullman committed
68 69

```php
Qiang Xue committed
70
// The following code will display "abc" when the event is triggered
Larry Ullman committed
71 72 73 74
// because $event->data contains the data passed as the 3rd argument to "on"
$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc');

function function_name($event) {
Qiang Xue committed
75
    echo $event->data;
Larry Ullman committed
76
}
Larry Ullman committed
77 78
```

Larry Ullman committed
79 80 81 82 83 84
Event Handler Order
-------------------

You may attach one or more handlers to a single event. When an event is triggered, the attached handlers
will be called in the order that they were attached to the event. If a handler needs to stop the invocation of the
handlers that follow it, it may set the [[yii\base\Event::handled]] property of the `$event` parameter to be true:
85

Qiang Xue committed
86 87 88 89 90 91 92 93
```php
$foo->on(Foo::EVENT_HELLO, function ($event) {
    $event->handled = true;
});
```

By default, a newly attached handler is appended to the existing handler queue for the event.
As a result, the handler will be called in the last place when the event is triggered.
Larry Ullman committed
94
To insert the new handler at the start of the handler queue so that the handler gets called first, you may call [[yii\base\Component::on()]], passing false for the fourth parameter `$append`:
95 96

```php
Qiang Xue committed
97 98 99
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // ...
}, $data, false);
100 101
```

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
Triggering Events <a name="triggering-events"></a>
-----------------

Events are triggered by calling the [[yii\base\Component::trigger()]] method. The method requires an *event name*,
and optionally an event object that describes the parameters to be passed to the event handlers. For example:

```php
namespace app\components;

use yii\base\Component;
use yii\base\Event;

class Foo extends Component
{
    const EVENT_HELLO = 'hello';

    public function bar()
    {
        $this->trigger(self::EVENT_HELLO);
    }
}
```

125
With the above code, any calls to `bar()` will trigger an event named `hello`.
126 127

> Tip: It is recommended to use class constants to represent event names. In the above example, the constant
128 129
  `EVENT_HELLO` represents the `hello` event. This approach has three benefits. First, it prevents typos. Second, it can make events recognizable for IDE
  auto-completion support. Third, you can tell what events are supported in a class by simply checking its constant declarations.
130

131
Sometimes when triggering an event you may want to pass along additional information to the event handlers.
132 133
For example, a mailer may want pass the message information to the handlers of the `messageSent` event so that the handlers
can know the particulars of the sent messages. To do so, you can provide an event object as the second parameter to
134 135
the [[yii\base\Component::trigger()]] method. The event object must be an instance of the [[yii\base\Event]] class,
or of a child class. For example:
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

```php
namespace app\components;

use yii\base\Component;
use yii\base\Event;

class MessageEvent extends Event
{
    public $message;
}

class Mailer extends Component
{
    const EVENT_MESSAGE_SENT = 'messageSent';

    public function send($message)
    {
        // ...sending $message...

        $event = new MessageEvent;
        $event->message = $message;
        $this->trigger(self::EVENT_MESSAGE_SENT, $event);
    }
}
```

163
When the [[yii\base\Component::trigger()]] method is called, it will call all handlers attached to
164 165
the named event.

Qiang Xue committed
166

Qiang Xue committed
167
Detaching Event Handlers <a name="detaching-event-handlers"></a>
Qiang Xue committed
168 169
------------------------

Larry Ullman committed
170
To detach a handler from an event, call the [[yii\base\Component::off()]] method. For example:
171 172

```php
Qiang Xue committed
173 174 175 176 177 178 179 180 181 182 183
// the handler is a global function
$foo->off(Foo::EVENT_HELLO, 'function_name');

// the handler is an object method
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);

// the handler is a static class method
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// the handler is an anonymous function
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
184 185
```

Qiang Xue committed
186
Note that in general you should not try to detach an anonymous function unless you store it
Larry Ullman committed
187
somewhere when it is attached to the event. In the above example, it is assumed that the anonymous
Qiang Xue committed
188 189 190 191 192 193 194
function is stored as a variable `$anonymousFunction`.

To detach ALL handlers from an event, simply call [[yii\base\Component::off()]] without the second parameter:

```php
$foo->off(Foo::EVENT_HELLO);
```
Larry Ullman committed
195

196

Qiang Xue committed
197
Class-Level Event Handlers <a name="class-level-event-handlers"></a>
198
--------------------------
199

Larry Ullman committed
200 201
The above subsections described how to attach a handler to an event on an *instance level*.
Sometimes, you may want to respond to an event triggered by *every* instance of a class instead of only by
202
a specific instance. Instead of attaching an event handler to every instance, you may attach the handler
Larry Ullman committed
203
on the *class level* by calling the static method [[yii\base\Event::on()]].
Larry Ullman committed
204

Larry Ullman committed
205 206 207
For example, an [Active Record](db-active-record.md) object will trigger an [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]]
event whenever it inserts a new record into the database. In order to track insertions done by *every*
[Active Record](db-active-record.md) object, you may use the following code:
208 209

```php
210 211 212 213 214 215 216
use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;

Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
    Yii::trace(get_class($event->sender) . ' is inserted');
});
217 218
```

Larry Ullman committed
219
The event handler will be invoked whenever an instance of [[yii\base\ActiveRecord|ActiveRecord]], or one of its child classes, triggers
220
the [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] event. In the handler, you can get the object
Larry Ullman committed
221
that triggered the event through `$event->sender`.
222

Larry Ullman committed
223
When an object triggers an event, it will first call instance-level handlers, followed by the class-level handlers.
224

Larry Ullman committed
225
You may trigger a *class-level* event by calling the static method [[yii\base\Event::trigger()]]. A class-level
226
event is not associated with a particular object. As a result, it will cause the invocation of class-level event
Larry Ullman committed
227
handlers only. For example:
228 229

```php
230 231 232 233 234 235 236
use yii\base\Event;

Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
    echo $event->sender;  // displays "app\models\Foo"
});

Event::trigger(Foo::className(), Foo::EVENT_HELLO);
237 238
```

Larry Ullman committed
239
Note that, in this case, `$event->sender` refers to the name of the class triggering the event instead of an object instance.
Larry Ullman committed
240

Larry Ullman committed
241 242
> Note: Because a class-level handler will respond to an event triggered by any instance of that class, or any child
  classes, you should use it carefully, especially if the class is a low-level base class, such as [[yii\base\Object]].
243

Larry Ullman committed
244
To detach a class-level event handler, call [[yii\base\Event::off()]]. For example:
245 246

```php
247 248 249 250 251 252 253 254
// detach $handler
Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);

// detach all handlers of Foo::EVENT_HELLO
Event::off(Foo::className(), Foo::EVENT_HELLO);
```


Qiang Xue committed
255
Global Events <a name="global-events"></a>
256 257
-------------

Larry Ullman committed
258 259
Yii supports a so-called *global event*, which is actually a trick based on the event mechanism described above.
The global event requires a globally accessible Singleton, such as the [application](structure-applications.md) instance itself.
260

Larry Ullman committed
261 262
To create the global evant, an event sender calls the Singleton's `trigger()` method
to trigger the event, instead of calling the sender's own `trigger()` method. Similarly, the event handlers are attached to the event on the Singleton. For example:
263 264 265 266 267 268 269 270

```php
use Yii;
use yii\base\Event;
use app\components\Foo;

Yii::$app->on('bar', function ($event) {
    echo get_class($event->sender);  // displays "app\components\Foo"
271
});
272 273

Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
274 275
```

Larry Ullman committed
276
A benefit of using global events is that you do not need an object when attaching a handler to the event
277
which will be triggered by the object. Instead, the handler attachment and the event triggering are both
Larry Ullman committed
278
done through the Singleton (e.g. the application instance).
279 280 281

However, because the namespace of the global events is shared by all parties, you should name the global events
wisely, such as introducing some sort of namespace (e.g. "frontend.mail.sent", "backend.mail.sent").