事件允許您在特定執行點將自訂程式碼注入到現有程式碼中。您可以將自訂程式碼附加到事件,以便在觸發事件時自動執行該程式碼。例如,郵件程式物件可能會在成功發送訊息時觸發 messageSent
事件。如果您想追蹤成功發送的訊息,您可以簡單地將追蹤程式碼附加到 messageSent
事件。
Yii 引入了一個名為 yii\base\Component 的基底類別來支援事件。如果一個類別需要觸發事件,它應該從 yii\base\Component 或其子類別擴展。
事件處理器是一個 PHP 回呼,當它附加到的事件被觸發時,它會被執行。您可以使用以下任何一種回呼
'trim'
;[$object, 'methodName']
;['ClassName', 'methodName']
;function ($event) { ... }
。事件處理器的簽名是
function ($event) {
// $event is an object of yii\base\Event or a child class
}
透過 $event
參數,事件處理器可以取得關於已發生事件的以下資訊
您可以通過呼叫 yii\base\Component::on() 方法將處理器附加到事件。例如
$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
});
您也可以透過 配置 附加事件處理器。有關更多詳細資訊,請參閱 配置 章節。
當附加事件處理器時,您可以將額外資料作為第三個參數提供給 yii\base\Component::on()。當事件被觸發且處理器被呼叫時,資料將提供給處理器。例如
// 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
參數的 yii\base\Event::$handled 屬性設定為 true
$foo->on(Foo::EVENT_HELLO, function ($event) {
$event->handled = true;
});
預設情況下,新附加的處理器會附加到事件的現有處理器佇列中。因此,當事件被觸發時,處理器將在最後的位置被呼叫。若要將新處理器插入到處理器佇列的開頭,以便首先呼叫處理器,您可以呼叫 yii\base\Component::on(),為第四個參數 $append
傳遞 false
$foo->on(Foo::EVENT_HELLO, function ($event) {
// ...
}, $data, false);
事件通過呼叫 yii\base\Component::trigger() 方法來觸發。該方法需要一個事件名稱,以及可選的事件物件,該物件描述要傳遞給事件處理器的參數。例如
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)
{
// ...sending $message...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
當呼叫 yii\base\Component::trigger() 方法時,它將呼叫附加到指定事件的所有處理器。
要從事件中分離處理器,請呼叫 yii\base\Component::off() 方法。例如
// 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);
請注意,一般來說,您不應該嘗試分離匿名函數,除非您在將其附加到事件時將其儲存在某個地方。在上面的範例中,假設匿名函數儲存為變數 $anonymousFunction
。
要從事件中分離所有處理器,只需呼叫 yii\base\Component::off() 而不帶第二個參數
$foo->off(Foo::EVENT_HELLO);
以上小節描述了如何在實例層級將處理器附加到事件。有時,您可能希望回應由類別的每個實例觸發的事件,而不是僅回應特定實例。您可以通過呼叫靜態方法 yii\base\Event::on(),在類別層級附加處理器,而不是將事件處理器附加到每個實例。
例如,當 Active Record 物件每次將新記錄插入資料庫時,都會觸發 EVENT_AFTER_INSERT 事件。為了追蹤每個 Active Record 物件執行的插入,您可以使用以下程式碼
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');
});
每當 ActiveRecord 的實例或其子類別之一觸發 EVENT_AFTER_INSERT 事件時,都會調用事件處理器。在處理器中,您可以通過 $event->sender
取得觸發事件的物件。
當物件觸發事件時,它將首先呼叫實例層級處理器,然後是類別層級處理器。
您可以通過呼叫靜態方法 yii\base\Event::trigger() 來觸發類別層級事件。類別層級事件與特定物件無關。因此,它將僅導致類別層級事件處理器的調用。例如
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);
請注意,在這種情況下,$event->sender
是 null
而不是物件實例。
注意:由於類別層級處理器將回應由該類別或任何子類別的任何實例觸發的事件,因此您應謹慎使用它,尤其是在該類別是低階基底類別(例如 yii\base\BaseObject)時。
要分離類別層級事件處理器,請呼叫 yii\base\Event::off()。例如
// detach $handler
Event::off(Foo::class, Foo::EVENT_HELLO, $handler);
// detach all handlers of Foo::EVENT_HELLO
Event::off(Foo::class, Foo::EVENT_HELLO);
還有一種更抽象的方式來處理事件。您可以為特殊事件創建一個單獨的介面,並在需要它的類別中實現它。
例如,我們可以創建以下介面
namespace app\interfaces;
interface DanceEventInterface
{
const EVENT_DANCE = 'dance';
}
以及實現它的兩個類別
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);
}
}
要處理由這些類別中的任何一個觸發的 EVENT_DANCE
,請呼叫 Event::on() 並將介面類別名稱作為第一個參數傳遞
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
});
您可以觸發這些類別的事件
// trigger event for Dog class
Event::trigger(Dog::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Developer class
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);
但請注意,您無法觸發所有實現該介面的類別
// DOES NOT WORK. Classes that implement this interface will NOT be triggered.
Event::trigger('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE);
要分離事件處理器,請呼叫 Event::off()。例如
// 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);
Yii 支援所謂的全域事件,這實際上是基於上述事件機制的技巧。全域事件需要全域可存取的 Singleton,例如 應用程式 實例本身。
要創建全域事件,事件發送者呼叫 Singleton 的 trigger()
方法來觸發事件,而不是呼叫發送者自己的 trigger()
方法。同樣地,事件處理器附加到 Singleton 上的事件。例如
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]));
使用全域事件的好處是,當您將處理器附加到將由物件觸發的事件時,您不需要物件。相反,處理器附加和事件觸發都通過 Singleton(例如應用程式實例)完成。
但是,由於全域事件的命名空間由所有參與者共享,因此您應該明智地命名全域事件,例如引入某種命名空間(例如 "frontend.mail.sent"、"backend.mail.sent")。
從 2.0.14 版本開始,您可以為多個與萬用字元模式匹配的事件設定事件處理器。例如
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);
});
萬用字元模式也可以用於類別層級事件。例如
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));
});
這允許您使用以下程式碼通過單個處理器捕獲所有應用程式事件
use yii\base\Event;
use Yii;
Event::on('*', '*', function ($event) {
// triggered for any event at any class
Yii::debug('trigger event: ' . $event->name);
});
注意:為事件處理器設定使用萬用字元可能會降低應用程式效能。如果可能,最好避免使用。
為了分離由萬用字元模式指定的事件處理器,您應該在呼叫 yii\base\Component::off() 或 yii\base\Event::off() 時重複相同的模式。請記住,在分離事件處理器期間傳遞萬用字元將僅分離為此萬用字元指定的處理器,而為常規事件名稱附加的處理器即使它們與模式匹配也將保留。例如
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'
發現錯字或您認為此頁面需要改進?
在 github 上編輯 !
註冊 或 登入 以發表評論。