mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			429 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
Events
 | 
						|
======
 | 
						|
 | 
						|
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,
 | 
						|
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.
 | 
						|
 | 
						|
Yii introduces a base class called [[yii\base\Component]] to support events. If a class needs to trigger
 | 
						|
events, it should extend from [[yii\base\Component]], or from a child class.
 | 
						|
 | 
						|
 | 
						|
Event Handlers <span id="event-handlers"></span>
 | 
						|
--------------
 | 
						|
 | 
						|
An event handler is a [PHP callback](https://www.php.net/manual/en/language.types.callable.php) that gets executed
 | 
						|
when the event it is attached to is triggered. You can use any of the following callbacks:
 | 
						|
 | 
						|
- 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 parentheses), 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., `['ClassName', 'methodName']`;
 | 
						|
- an anonymous function, e.g., `function ($event) { ... }`.
 | 
						|
 | 
						|
The signature of an event handler is:
 | 
						|
 | 
						|
```php
 | 
						|
function ($event) {
 | 
						|
    // $event is an object of yii\base\Event or a child class
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
Through the `$event` parameter, an event handler may get the following information about the event that occurred:
 | 
						|
 | 
						|
- [[yii\base\Event::name|event name]];
 | 
						|
- [[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).
 | 
						|
 | 
						|
 | 
						|
Attaching Event Handlers <span id="attaching-event-handlers"></span>
 | 
						|
------------------------
 | 
						|
 | 
						|
You can attach a handler to an event by calling the [[yii\base\Component::on()]] method. For example:
 | 
						|
 | 
						|
```php
 | 
						|
$foo = new Foo();
 | 
						|
 | 
						|
// this handler is a global function
 | 
						|
$foo->on(Foo::EVENT_HELLO, 'function_name');
 | 
						|
 | 
						|
// this handler is an object method
 | 
						|
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
 | 
						|
 | 
						|
// this handler is a static class method
 | 
						|
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
 | 
						|
 | 
						|
// this handler is an anonymous function
 | 
						|
$foo->on(Foo::EVENT_HELLO, function ($event) {
 | 
						|
    // event handling logic
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
You may also attach event handlers through [configurations](concept-configurations.md). For more details, please
 | 
						|
refer to the [Configurations](concept-configurations.md#configuration-format) section.
 | 
						|
 | 
						|
 | 
						|
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:
 | 
						|
 | 
						|
```php
 | 
						|
// The following code will display "abc" when the event is triggered
 | 
						|
// 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) {
 | 
						|
    echo $event->data;
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
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`:
 | 
						|
 | 
						|
```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, you may call [[yii\base\Component::on()]], passing `false` for the fourth parameter `$append`:
 | 
						|
 | 
						|
```php
 | 
						|
$foo->on(Foo::EVENT_HELLO, function ($event) {
 | 
						|
    // ...
 | 
						|
}, $data, false);
 | 
						|
```
 | 
						|
 | 
						|
Triggering Events <span id="triggering-events"></span>
 | 
						|
-----------------
 | 
						|
 | 
						|
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);
 | 
						|
    }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
With the above code, any calls to `bar()` will trigger an event named `hello`.
 | 
						|
 | 
						|
> Tip: It is recommended to use class constants to represent event names. In the above example, the constant
 | 
						|
  `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.
 | 
						|
 | 
						|
Sometimes when triggering an event you may want to pass along additional information to the event handlers.
 | 
						|
For example, a mailer may want to 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
 | 
						|
the [[yii\base\Component::trigger()]] method. The event object must be an instance of the [[yii\base\Event]] class
 | 
						|
or a child class. For example:
 | 
						|
 | 
						|
```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);
 | 
						|
    }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
When the [[yii\base\Component::trigger()]] method is called, it will call all handlers attached to
 | 
						|
the named event.
 | 
						|
 | 
						|
 | 
						|
Detaching Event Handlers <span id="detaching-event-handlers"></span>
 | 
						|
------------------------
 | 
						|
 | 
						|
To detach a handler from an event, call the [[yii\base\Component::off()]] method. For example:
 | 
						|
 | 
						|
```php
 | 
						|
// 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);
 | 
						|
```
 | 
						|
 | 
						|
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, it is assumed that 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);
 | 
						|
```
 | 
						|
 | 
						|
 | 
						|
Class-Level Event Handlers <span id="class-level-event-handlers"></span>
 | 
						|
--------------------------
 | 
						|
 | 
						|
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
 | 
						|
a specific instance. Instead of attaching an event handler to every instance, you may attach the handler
 | 
						|
on the *class level* by calling the static method [[yii\base\Event::on()]].
 | 
						|
 | 
						|
For example, an [Active Record](db-active-record.md) object will trigger an [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|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:
 | 
						|
 | 
						|
```php
 | 
						|
use Yii;
 | 
						|
use yii\base\Event;
 | 
						|
use yii\db\ActiveRecord;
 | 
						|
 | 
						|
Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
 | 
						|
    Yii::debug(get_class($event->sender) . ' is inserted');
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
The event handler will be invoked whenever an instance of [[yii\db\ActiveRecord|ActiveRecord]], or one of its child classes, triggers
 | 
						|
the [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] event. In the handler, you can get the object
 | 
						|
that triggered the event through `$event->sender`.
 | 
						|
 | 
						|
When an object triggers an event, it will first call instance-level handlers, followed by the class-level handlers.
 | 
						|
 | 
						|
You may trigger a *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:
 | 
						|
 | 
						|
```php
 | 
						|
use yii\base\Event;
 | 
						|
 | 
						|
Event::on(Foo::class, Foo::EVENT_HELLO, function ($event) {
 | 
						|
    var_dump($event->sender);  // displays "null"
 | 
						|
});
 | 
						|
 | 
						|
Event::trigger(Foo::class, Foo::EVENT_HELLO);
 | 
						|
```
 | 
						|
 | 
						|
Note that, in this case, `$event->sender` is `null` instead of an object instance.
 | 
						|
 | 
						|
> 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\BaseObject]].
 | 
						|
 | 
						|
To detach a class-level event handler, call [[yii\base\Event::off()]]. For example:
 | 
						|
 | 
						|
```php
 | 
						|
// detach $handler
 | 
						|
Event::off(Foo::class, Foo::EVENT_HELLO, $handler);
 | 
						|
 | 
						|
// detach all handlers of Foo::EVENT_HELLO
 | 
						|
Event::off(Foo::class, Foo::EVENT_HELLO);
 | 
						|
```
 | 
						|
 | 
						|
 | 
						|
Events using interfaces <span id="interface-level-event-handlers"></span>
 | 
						|
-------------
 | 
						|
 | 
						|
There is even more abstract way to deal with events. You can create a separated interface for the special event and
 | 
						|
implement it in classes, where you need it.
 | 
						|
 | 
						|
For example, we can create the following interface:
 | 
						|
 | 
						|
```php
 | 
						|
namespace app\interfaces;
 | 
						|
 | 
						|
interface DanceEventInterface
 | 
						|
{
 | 
						|
    const EVENT_DANCE = 'dance';
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
And two classes, that implement it:
 | 
						|
 | 
						|
```php
 | 
						|
class Dog extends Component implements DanceEventInterface
 | 
						|
{
 | 
						|
    public function meetBuddy()
 | 
						|
    {
 | 
						|
        echo "Woof!";
 | 
						|
        $this->trigger(DanceEventInterface::EVENT_DANCE);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class Developer extends Component implements DanceEventInterface
 | 
						|
{
 | 
						|
    public function testsPassed()
 | 
						|
    {
 | 
						|
        echo "Yay!";
 | 
						|
        $this->trigger(DanceEventInterface::EVENT_DANCE);
 | 
						|
    }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
To handle the `EVENT_DANCE`, triggered by any of these classes, call [[yii\base\Event::on()|Event::on()]] and
 | 
						|
pass the interface class name as the first argument:
 | 
						|
 | 
						|
```php
 | 
						|
Event::on('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
 | 
						|
    Yii::debug(get_class($event->sender) . ' just danced'); // Will log that Dog or Developer danced
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
You can trigger the event of those classes:
 | 
						|
 | 
						|
```php
 | 
						|
// trigger event for Dog class
 | 
						|
Event::trigger(Dog::class, DanceEventInterface::EVENT_DANCE);
 | 
						|
 | 
						|
// trigger event for Developer class
 | 
						|
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);
 | 
						|
```
 | 
						|
 | 
						|
But please notice, that you can not trigger all the classes, that implement the interface:
 | 
						|
 | 
						|
```php
 | 
						|
// DOES NOT WORK. Classes that implement this interface will NOT be triggered.
 | 
						|
Event::trigger('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE);
 | 
						|
```
 | 
						|
 | 
						|
To detach event handler, call [[yii\base\Event::off()|Event::off()]]. For example:
 | 
						|
 | 
						|
```php
 | 
						|
// detaches $handler
 | 
						|
Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler);
 | 
						|
 | 
						|
// detaches all handlers of DanceEventInterface::EVENT_DANCE
 | 
						|
Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE);
 | 
						|
```
 | 
						|
 | 
						|
 | 
						|
Global Events <span id="global-events"></span>
 | 
						|
-------------
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
To create the global event, 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:
 | 
						|
 | 
						|
```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"
 | 
						|
});
 | 
						|
 | 
						|
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
 | 
						|
```
 | 
						|
 | 
						|
A benefit of using global events is that you do not need an 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").
 | 
						|
 | 
						|
 | 
						|
Wildcard Events <span id="wildcard-events"></span>
 | 
						|
---------------
 | 
						|
 | 
						|
Since 2.0.14 you can setup event handler for multiple events matching wildcard pattern.
 | 
						|
For example:
 | 
						|
 | 
						|
```php
 | 
						|
use Yii;
 | 
						|
 | 
						|
$foo = new Foo();
 | 
						|
 | 
						|
$foo->on('foo.event.*', function ($event) {
 | 
						|
    // triggered for any event, which name starts on 'foo.event.'
 | 
						|
    Yii::debug('trigger event: ' . $event->name);
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
Wildcard patterns can be used for class-level events as well. For example:
 | 
						|
 | 
						|
```php
 | 
						|
use yii\base\Event;
 | 
						|
use Yii;
 | 
						|
 | 
						|
Event::on('app\models\*', 'before*', function ($event) {
 | 
						|
    // triggered for any class in namespace 'app\models' for any event, which name starts on 'before'
 | 
						|
    Yii::debug('trigger event: ' . $event->name . ' for class: ' . get_class($event->sender));
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
This allows you catching all application events by single handler using following code:
 | 
						|
 | 
						|
```php
 | 
						|
use yii\base\Event;
 | 
						|
use Yii;
 | 
						|
 | 
						|
Event::on('*', '*', function ($event) {
 | 
						|
    // triggered for any event at any class
 | 
						|
    Yii::debug('trigger event: ' . $event->name);
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
> Note: usage wildcards for event handlers setup may reduce the application performance.
 | 
						|
  It is better to be avoided if possible.
 | 
						|
 | 
						|
In order to detach event handler specified by wildcard pattern, you should repeat same pattern at
 | 
						|
[[yii\base\Component::off()]] or [[yii\base\Event::off()]] invocation. Keep in mind that passing wildcard
 | 
						|
during detaching of event handler will detach only the handler specified for this wildcard, while handlers
 | 
						|
attached for regular event names will remain even if they match the pattern. For example:
 | 
						|
 | 
						|
```php
 | 
						|
use Yii;
 | 
						|
 | 
						|
$foo = new Foo();
 | 
						|
 | 
						|
// attach regular handler
 | 
						|
$foo->on('event.hello', function ($event) {
 | 
						|
    echo 'direct-handler'
 | 
						|
});
 | 
						|
 | 
						|
// attach wildcard handler
 | 
						|
$foo->on('*', function ($event) {
 | 
						|
    echo 'wildcard-handler'
 | 
						|
});
 | 
						|
 | 
						|
// detach wildcard handler only!
 | 
						|
$foo->off('*');
 | 
						|
 | 
						|
$foo->trigger('event.hello'); // outputs: 'direct-handler'
 | 
						|
```
 |