事件


事件

事件可以將自定義程式碼“注入”到現有程式碼中的特定執行點。附加自定義程式碼到某個事件,當這個事件被觸發時,這些程式碼就會自動執行。例如,郵件程式物件成功發出訊息時可觸發 messageSent 事件。如想追蹤成功傳送的訊息,可以附加相應追蹤程式碼到 messageSent 事件。

Yii 引入了名為 [[yii\base\Component]] 的基礎類別以支援事件。如果一個類需要觸發事件就應該繼承 [[yii\base\Component]] 或其子類。

事件處理器(Event Handlers)

事件處理器是一個PHP 回撥函式,當它所附加到的事件被觸發時它就會執行。可以使用以下回撥函式之一:

  • 字串形式指定的 PHP 全域性函式,如 'trim' ;
  • 物件名和方法名陣列形式指定的物件方法,如 [$object, $method] ;
  • 類名和方法名陣列形式指定的靜態類方法,如 [$class, $method] ;
  • 匿名函式,如 function ($event) { ... } 。

事件處理器的格式是:

  1. function ($event) { 
  2.     // $event 是 yii\\base\\Event 或其子類的物件 
  3. }

通過 $event 引數,事件處理器就獲得了以下有關事件的資訊:

  • [[yii\base\Event::name|event name]]:事件名
  • [[yii\base\Event::sender|event sender]]:呼叫 trigger() 方法的物件
  • [[yii\base\Event::data|custom data]]:附加事件處理器時傳入的資料,預設為空,後文詳述

附加事件處理器

呼叫 [[yii\base\Component::on()]] 方法來附加處理器到事件上。如:

  1. $foo = new Foo; 
  2.  
  3. // 處理器是全域性函式 
  4. $foo->on(Foo::EVENT_HELLO, 'function_name'); 
  5.  
  6. // 處理器是物件方法 
  7. $foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); 
  8.  
  9. // 處理器是靜態類方法 
  10. $foo->on(Foo::EVENT_HELLO, ['app\\components\\Bar', 'methodName']); 
  11.  
  12. // 處理器是匿名函式 
  13. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  14.     //事件處理邏輯 
  15. });

附加事件處理器時可以提供額外資料作為 [[yii\base\Component::on()]] 方法的第三個引數。資料在事件被觸發和處理器被呼叫時能被處理器使用。如:

  1. // 當事件被觸發時以下程式碼顯示 "abc" 
  2. // 因為 $event->data 包括被傳遞到 "on" 方法的資料 
  3. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  4.     echo $event->data; 
  5. }, 'abc');

時間處理器順序

可以附加一個或多個處理器到一個事件。當事件被觸發,已附加的處理器將按附加次序依次呼叫。如果某個處理器需要停止其後的處理器呼叫,可以設定 $event 引數的 [yii\base\Event::handled]] 屬性為真,如下:

  1. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  2.     $event->handled = true; 
  3. });

預設新附加的事件處理器排在已存在處理器佇列的最後。因此,這個處理器將在事件被觸發時最後一個呼叫。在處理器佇列最前面插入新處理器將使該處理器最先呼叫,可以傳遞第四個引數 $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]] 類或其子類的範例。如:

  1. namespace app\\components; 
  2.  
  3. use yii\\base\\Component; 
  4. use yii\\base\\Event; 
  5.  
  6. class MessageEvent extends Event 
  7.     public $message; 
  8.  
  9. class Mailer extends Component 
  10.     const EVENT_MESSAGE_SENT = 'messageSent'; 
  11.  
  12.     public function send($message) 
  13.     { 
  14.         // ...傳送 $message 的邏輯... 
  15.  
  16.         $event = new MessageEvent; 
  17.         $event->message = $message; 
  18.         $this->trigger(self::EVENT_MESSAGE_SENT, $event); 
  19.     } 
  20. }

當 [[yii\base\Component::trigger()]] 方法被呼叫時,它將呼叫所有附加到命名事件(trigger 方法第一個引數)的事件處理器。

移除事件處理器

從事件移除處理器,呼叫 [[yii\base\Component::off()]] 方法。如:

  1. // 處理器是全域性函式 
  2. $foo->off(Foo::EVENT_HELLO, 'function_name'); 
  3.  
  4. // 處理器是物件方法 
  5. $foo->off(Foo::EVENT_HELLO, [$object, 'methodName']); 
  6.  
  7. // 處理器是靜態類方法 
  8. $foo->off(Foo::EVENT_HELLO, ['app\\components\\Bar', 'methodName']); 
  9.  
  10. // 處理器是匿名函式 
  11. $foo->off(Foo::EVENT_HELLO, $anonymousFunction);

注意當匿名函式附加到事件後一般不要嘗試移除匿名函式,除非你在某處儲存了它。以上範例中,假設匿名函式儲存為變數 $anonymousFunction 。

移除事件的全部處理器,簡單呼叫 [[yii\base\Component::off()]] 即可,不需要第二個引數:

  1. $foo->off(Foo::EVENT_HELLO);

類級別的事件處理器

以上部分,我們敘述了在範例級別如何附加處理器到事件。有時想要一個類的所有範例而不是一個指定的範例都響應一個被觸發的事件,並不是一個個附加事件處理器到每個範例,而是通過呼叫靜態方法 [[yii\base\Event::on()]] 在類級別附加處理器。

例如,活動記錄物件要在每次往資料庫新增一條新記錄時觸發一個 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]] 事件。要追蹤每個活動記錄物件的新增記錄完成情況,應如下寫程式碼:

  1. use Yii; 
  2. use yii\\base\\Event; 
  3. use yii\\db\\ActiveRecord; 
  4.  
  5. Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { 
  6.     Yii::trace(get_class($event->sender) . ' is inserted'); 
  7. });

每當 [[yii\base\ActiveRecord|ActiveRecord]] 或其子類的範例觸發 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件時,這個事件處理器都會執行。在這個處理器中,可以通過 $event->sender 獲取觸發事件的物件。

當物件觸發事件時,它首先呼叫範例級別的處理器,然後才會呼叫類級別處理器。

可呼叫靜態方法[[yii\base\Event::trigger()]]來觸發一個類級別事件。類級別事件不與特定物件相關聯。因此,它只會引起類級別事件處理器的呼叫。如:

  1. use yii\\base\\Event; 
  2.  
  3. Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { 
  4.     echo $event->sender;  // 顯示 "app\\models\\Foo" 
  5. }); 
  6.  
  7. Event::trigger(Foo::className(), Foo::EVENT_HELLO);

注意這種情況下 $event->sender 指向觸發事件的類名而不是物件範例。

注意:因為類級別的處理器響應類和其子類的所有範例觸發的事件,必須謹慎使用,尤其是底層的基礎類別,如 [[yii\base\Object]]。

移除類級別的事件處理器只需呼叫[[yii\base\Event::off()]],如:

  1. // 移除 $handler 
  2. Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); 
  3.  
  4. // 移除 Foo::EVENT_HELLO 事件的全部處理器 
  5. Event::off(Foo::className(), Foo::EVENT_HELLO);

全域性事件

所謂全域性事件實際上是一個基於以上敘述的事件機制的戲法。它需要一個全域性可存取的單例,如應用範例。

事件觸發者不呼叫其自身的 trigger() 方法,而是呼叫單例的 trigger() 方法來觸發全域性事件。類似地,事件處理器被附加到單例的事件。如:

  1. use Yii; 
  2. use yii\\base\\Event; 
  3. use app\\components\\Foo; 
  4.  
  5. Yii::$app->on('bar', function ($event) { 
  6.     echo get_class($event->sender);  // 顯示 "app\\components\\Foo" 
  7. }); 
  8.  
  9. Yii::$app->trigger('bar', new Event(['sender' => new Foo]));

全域性事件的一個好處是當附加處理器到一個物件要觸發的事件時,不需要產生該物件。相反,處理器附加和事件觸發都通過單例(如應用範例)完成。

然而,因為全域性事件的名稱空間由各方共用,應合理命名全域性事件,如引入一些名稱空間(例:"frontend.mail.sent", "backend.mail.sent")。