事件
事件
事件可以將自定義代碼「注入」到現有代碼中的特定執行點。附加自定義代碼到某個事件,當這個事件被觸發時,這些代碼就會自動執行。例如,郵件程序對象成功發出消息時可觸發 messageSent
事件。如想追蹤成功發送的消息,可以附加相應追蹤代碼到 messageSent
事件。
Yii 引入了名爲 [[yii\base\Component]] 的基類以支持事件。如果一個類需要觸發事件就應該繼承 [[yii\base\Component]] 或其子類。
事件處理器(Event Handlers)
事件處理器是一個PHP 回調函數,當它所附加到的事件被觸發時它就會執行。可以使用以下回調函數之一:
- 字符串形式指定的 PHP 全局函數,如 'trim' ;
- 對象名和方法名數組形式指定的對象方法,如 [$object, $method] ;
- 類名和方法名數組形式指定的靜態類方法,如 [$class, $method] ;
- 匿名函數,如 function ($event) { ... } 。
事件處理器的格式是:
- function ($event) {
- // $event 是 yii\\base\\Event 或其子類的對象
- }
通過 $event
參數,事件處理器就獲得了以下有關事件的信息:
- [[yii\base\Event::name|event name]]:事件名
- [[yii\base\Event::sender|event sender]]:調用 trigger() 方法的對象
- [[yii\base\Event::data|custom data]]:附加事件處理器時傳入的數據,默認爲空,後文詳述
附加事件處理器
調用 [[yii\base\Component::on()]] 方法來附加處理器到事件上。如:
$foo = new Foo;
// 處理器是全局函數
$foo->on(Foo::EVENT_HELLO, 'function_name');
// 處理器是對象方法
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// 處理器是靜態類方法
$foo->on(Foo::EVENT_HELLO, ['app\\components\\Bar', 'methodName']);
// 處理器是匿名函數
$foo->on(Foo::EVENT_HELLO, function ($event) {
//事件處理邏輯
});
附加事件處理器時可以提供額外數據作爲 [[yii\base\Component::on()]] 方法的第三個參數。數據在事件被觸發和處理器被調用時能被處理器使用。如:
- // 當事件被觸發時以下代碼顯示 "abc"
- // 因爲 $event->data 包括被傳遞到 "on" 方法的數據
- $foo->on(Foo::EVENT_HELLO, function ($event) {
- echo $event->data;
- }, 'abc');
時間處理器順序
可以附加一個或多個處理器到一個事件。當事件被觸發,已附加的處理器將按附加次序依次調用。如果某個處理器需要停止其後的處理器調用,可以設置 $event
參數的 [yii\base\Event::handled]] 屬性爲真,如下:
- $foo->on(Foo::EVENT_HELLO, function ($event) {
- $event->handled = true;
- });
默認新附加的事件處理器排在已存在處理器隊列的最後。因此,這個處理器將在事件被觸發時最後一個調用。在處理器隊列最前面插入新處理器將使該處理器最先調用,可以傳遞第四個參數 $append
爲假並調用 [[yii\base\Component::on()]] 方法實現:
``php $foo->on(Foo::EVENT_HELLO, function ($event) { // 這個處理器將被插入到處理器隊列的第一位... }, $data, false);
觸發事件
----------
事件通過調用 [[yii\base\Component::trigger()]] 方法觸發,此方法須傳遞**事件名**,還可以傳遞一個事件對象,用來傳遞參數到事件處理器。如:
```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);
}
}
以上代碼當調用 bar()
,它將觸發名爲 hello
的事件。
提示:推薦使用類常量來表示事件名。上例中,常量
EVENT_HELLO
用來表示hello
。這有兩個好處。第一,它可以防止拼寫錯誤並支持 IDE 的自動完成。第二,只要簡單檢查常量聲明就能瞭解一個類支持哪些事件。
有時想要在觸發事件時同時傳遞一些額外信息到事件處理器。例如,郵件程序要傳遞消息信息到 messageSent
事件的處理器以便處理器瞭解哪些消息被髮送了。爲此,可以提供一個事件對象作爲 [[yii\base\Component::trigger()]] 方法的第二個參數。這個事件對象必須是 [[yii\base\Event]] 類或其子類的實例。如:
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)
{
// ...發送 $message 的邏輯...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
當 [[yii\base\Component::trigger()]] 方法被調用時,它將調用所有附加到命名事件(trigger 方法第一個參數)的事件處理器。
移除事件處理器
從事件移除處理器,調用 [[yii\base\Component::off()]] 方法。如:
// 處理器是全局函數
$foo->off(Foo::EVENT_HELLO, 'function_name');
// 處理器是對象方法
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
// 處理器是靜態類方法
$foo->off(Foo::EVENT_HELLO, ['app\\components\\Bar', 'methodName']);
// 處理器是匿名函數
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
注意當匿名函數附加到事件後一般不要嘗試移除匿名函數,除非你在某處存儲了它。以上示例中,假設匿名函數存儲爲變量 $anonymousFunction
。
移除事件的全部處理器,簡單調用 [[yii\base\Component::off()]] 即可,不需要第二個參數:
- $foo->off(Foo::EVENT_HELLO);
類級別的事件處理器
以上部分,我們敘述了在實例級別如何附加處理器到事件。有時想要一個類的所有實例而不是一個指定的實例都響應一個被觸發的事件,並不是一個個附加事件處理器到每個實例,而是通過調用靜態方法 [[yii\base\Event::on()]] 在類級別附加處理器。
例如,活動記錄對象要在每次往數據庫新增一條新記錄時觸發一個 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]] 事件。要追蹤每個活動記錄對象的新增記錄完成情況,應如下寫代碼:
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');
});
每當 [[yii\base\ActiveRecord|ActiveRecord]] 或其子類的實例觸發 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件時,這個事件處理器都會執行。在這個處理器中,可以通過 $event->sender
獲取觸發事件的對象。
當對象觸發事件時,它首先調用實例級別的處理器,然後纔會調用類級別處理器。
可調用靜態方法[[yii\base\Event::trigger()]]來觸發一個類級別事件。類級別事件不與特定對象相關聯。因此,它只會引起類級別事件處理器的調用。如:
use yii\\base\\Event;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
echo $event->sender; // 顯示 "app\\models\\Foo"
});
Event::trigger(Foo::className(), Foo::EVENT_HELLO);
注意這種情況下 $event->sender
指向觸發事件的類名而不是對象實例。
注意:因爲類級別的處理器響應類和其子類的所有實例觸發的事件,必須謹慎使用,尤其是底層的基類,如 [[yii\base\Object]]。
移除類級別的事件處理器只需調用[[yii\base\Event::off()]],如:
// 移除 $handler
Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);
// 移除 Foo::EVENT_HELLO 事件的全部處理器
Event::off(Foo::className(), Foo::EVENT_HELLO);
全局事件
所謂全局事件實際上是一個基於以上敘述的事件機制的戲法。它需要一個全局可訪問的單例,如應用實例。
事件觸發者不調用其自身的 trigger()
方法,而是調用單例的 trigger()
方法來觸發全局事件。類似地,事件處理器被附加到單例的事件。如:
use Yii;
use yii\\base\\Event;
use app\\components\\Foo;
Yii::$app->on('bar', function ($event) {
echo get_class($event->sender); // 顯示 "app\\components\\Foo"
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
全局事件的一個好處是當附加處理器到一個對象要觸發的事件時,不需要產生該對象。相反,處理器附加和事件觸發都通過單例(如應用實例)完成。
然而,因爲全局事件的命名空間由各方共享,應合理命名全局事件,如引入一些命名空間(例:"frontend.mail.sent", "backend.mail.sent")。