concept-events.md 10.4 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
10
events, it should extend from [[yii\base\Component]] or a child class.
11

Alexander Makarov committed
12

Qiang Xue committed
13
Triggering Events <a name="triggering-events"></a>
Qiang Xue committed
14
-----------------
Alexander Makarov committed
15

16 17
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:
Alexander Makarov committed
18

Qiang Xue committed
19 20
```php
namespace app\components;
Alexander Makarov committed
21

Qiang Xue committed
22 23
use yii\base\Component;
use yii\base\Event;
Alexander Makarov committed
24

Qiang Xue committed
25 26 27
class Foo extends Component
{
    const EVENT_HELLO = 'hello';
Alexander Makarov committed
28

Qiang Xue committed
29 30 31 32 33
    public function bar()
    {
        $this->trigger(self::EVENT_HELLO);
    }
}
Alexander Makarov committed
34
```
35

Qiang Xue committed
36 37 38
In the above code, when you call `bar()`, it will trigger an event named `hello`.

> Tip: It is recommended to use class constants to represent event names. In the above example, the constant
39
  `EVENT_HELLO` is used to represent `hello`. This approach has two benefits. First, it prevents typos and can impact IDE
Qiang Xue committed
40 41
  auto-completion support. Second, you can tell what events are supported by a class by simply checking the constant
  declarations.
42

43
Sometimes when triggering an event, you may want to pass along additional information to the event handlers.
Qiang Xue committed
44
For example, a mailer may want pass the message information to the handlers of the `messageSent` event so that the handlers
45
can know the particulars of the sent messages. To do so, you can provide an event object as the second parameter to
Qiang Xue committed
46
the [[yii\base\Component::trigger()]] method. The event object must be an instance of the [[yii\base\Event]] class
47
or a child class. For example:
48 49

```php
Qiang Xue committed
50
namespace app\components;
51

Qiang Xue committed
52 53
use yii\base\Component;
use yii\base\Event;
54

Qiang Xue committed
55 56 57 58
class MessageEvent extends Event
{
    public $message;
}
59

Qiang Xue committed
60 61 62 63 64 65 66 67 68 69 70 71 72
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);
    }
}
73 74
```

Qiang Xue committed
75 76
When the [[yii\base\Component::trigger()]] method is called, it will call handlers that are attached to
the named event.
77

Alexander Makarov committed
78

Qiang Xue committed
79
Event Handlers <a name="event-handlers"></a>
Qiang Xue committed
80 81 82 83
--------------

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

Qiang Xue committed
85 86 87 88
- a global PHP function specified in terms of a string, e.g., `'trim()'`;
- an object method specified in terms of an array of an object and a method name, e.g., `[$object, $method]`;
- a static class method specified in terms of an array of a class name and a method name, e.g., `[$class, $method]`;
- an anonymous function, e.g., `function ($event) { ... }`.
Alexander Makarov committed
89

Qiang Xue committed
90
The signature of an event handler is:
Alexander Makarov committed
91 92

```php
Qiang Xue committed
93 94 95
function ($event) {
    // $event is an object of yii\base\Event or its child class
}
Alexander Makarov committed
96
```
97

Qiang Xue committed
98
Through the `$event` parameter, an event handler may get the following information about an event:
99

Qiang Xue committed
100 101 102
- [[yii\base\Event::name|event name]]
- [[yii\base\Event::sender|event sender]]: the object whose `trigger()` method is called.
- [[yii\base\Event::data|custom data]]: the data that is provided when attaching the event handler (to be explained shortly).
Larry Ullman committed
103

Larry Ullman committed
104

Qiang Xue committed
105
Attaching Event Handlers <a name="attaching-event-handlers"></a>
Qiang Xue committed
106 107 108
------------------------

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

```php
Qiang Xue committed
111 112 113 114
$foo = new Foo;

// the handler is a global function
$foo->on(Foo::EVENT_HELLO, 'function_name');
Larry Ullman committed
115

Qiang Xue committed
116 117
// the handler is an object method
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
Larry Ullman committed
118

Qiang Xue committed
119 120 121 122 123 124 125
// the handler is a static class method
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// the handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // event handling logic
});
Larry Ullman committed
126 127
```

128
You may also attach event handlers through [configurations](concept-configurations.md). For more details, please
129
refer to the [Configurations](concept-configurations.md#configuration-format) section.
130 131


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

```php
Qiang Xue committed
136 137 138 139 140
// The following code will display "abc" when the event is triggered
// because $event->data contains the data passed to "on"
$foo->on(Foo::EVENT_HELLO, function ($event) {
    echo $event->data;
}, 'abc');
Larry Ullman committed
141 142
```

Qiang Xue committed
143 144 145 146
You may attach one or multiple handlers to a single event. When an event is triggered, the attached handlers
will be called in the order they are attached to the event. If a handler needs to stop the invocation of the
handlers behind it, it may set the [[yii\base\Event::handled]] property of the `$event` parameter to be true,
like the following,
147

Qiang Xue committed
148 149 150 151 152 153 154 155 156 157
```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.
To insert the new handler at the start of the handler queue so that the handler gets called first, y
ou may call [[yii\base\Component::on()]] by passing the fourth parameter `$append` as false:
158 159

```php
Qiang Xue committed
160 161 162
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // ...
}, $data, false);
163 164
```

Qiang Xue committed
165

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

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

```php
Qiang Xue committed
172 173 174 175 176 177 178 179 180 181 182
// 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);
183 184
```

Qiang Xue committed
185 186 187 188 189 190 191 192 193
Note that in general you should not try to detach an anonymous function unless you store it
somewhere when it is attached to the event. In the above example, we assume the anonymous
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
194

195

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

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

204 205 206
For example, an [Active Record](db-active-record.md) object will trigger a [[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 write the following code:
207 208

```php
209 210 211 212 213 214 215
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');
});
216 217
```

218 219 220 221 222 223 224 225 226
The event handler will get invoked whenever an instance of [[yii\base\ActiveRecord|ActiveRecord]] or its child class triggers
the [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] event. In the handler, you can get the object
that triggers the event through `$event->sender`.

When an object triggers an event, it will first call instance-level handlers, followed by class-level handlers.

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

```php
229 230 231 232 233 234 235
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);
236 237
```

238
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
239

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

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

```php
246 247 248 249 250 251 252 253
// 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
254
Global Events <a name="global-events"></a>
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
-------------

The so-called *global event* is actually a trick based on the event mechanism described above.
It requires a globally accessible singleton, such as the [application](structure-applications.md) instance.

An event sender, instead of calling its own `trigger()` method, will call the singleton's `trigger()` method
to trigger the event. Similarly, the event handlers are attached to the event of the singleton. For example,

```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"
270
});
271 272

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

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

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").