您好,登錄后才能下訂單哦!
這篇文章給大家介紹PHP中Yii框架的event事件機制的原理是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
Yii的event機制
YII的事件機制,是其比較獨特之處,合理使用好事件機制,會使各個組件之間的耦合更為松散,利于團體協作開發。
何時需要使用事件,如何給事件綁定事件處理函數,以及如何觸發事件,與其它語言是有較大的差別的。例如Javascript中,可以使用
$(‘#id').on("click",function() {});
方式給DOM元素綁定處理函數,當DOM元素上發生指定的事件(如click)時,將自動執行設定的函數。
但是PHP是服務器端的腳本語言,就不存在自動觸發事件之說,所以和Javascript對比,YII中的事件是需要手動觸發的。一般來說,要實現YII組件的事件機制,需要以下幾步:
定義事件名稱,其實就是級組件定義一個on開頭的方法,其中的代碼是固定的,如:
public function onBeginRequest($event){ $this->raiseEvent('onBeginRequest',$event); }
即函數名與事件名是一致的。此步的作用就是將綁定在此事件上的處理函數逐個執行。寫這一系列的播客,算是一個整理,所以我寫細一點,現在把raiseEvent方法的代碼貼出來。
/** * Raises an event. * This method represents the happening of an event. It invokes * all attached handlers for the event. * @param string $name the event name * @param CEvent $event the event parameter * @throws CException if the event is undefined or an event handler is invalid. */ public function raiseEvent($name,$event){ $name=strtolower($name); //_e這個數組用來存所有事件信息 if(isset($this->_e[$name])) { foreach($this->_e[$name] as $handler) { if(is_string($handler)) call_user_func($handler,$event); elseif(is_callable($handler,true)){ if(is_array($handler)){ // an array: 0 - object, 1 - method name list($object,$method)=$handler; if(is_string($object)) // static method call call_user_func($handler,$event); elseif(method_exists($object,$method)) $object->$method($event); else throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".', array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1]))); } else // PHP 5.3: anonymous function call_user_func($handler,$event); } else throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".', array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler)))); // stop further handling if param.handled is set true if(($event instanceof CEvent) && $event->handled) return; } } elseif(YII_DEBUG && !$this->hasEvent($name)) throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.', array('{class}'=>get_class($this), '{event}'=>$name))); }
事件處理器(Event Handlers)
事件處理器是一個PHP 回調函數,當它所附加到的事件被觸發時它就會執行。可以使用以下回調函數之一:
字符串形式指定的 PHP 全局函數,如 'trim' ;
對象名和方法名數組形式指定的對象方法,如 [$object, $method] ;
類名和方法名數組形式指定的靜態類方法,如 [$class, $method] ;
匿名函數,如 function ($event) { ... } 。
事件處理器的格式是:
function ($event) { // $event 是 yii\base\Event 或其子類的對象 }
通過 $event 參數,事件處理器就獲得了以下有關事件的信息:
yii\base\Event::name:事件名
yii\base\Event::sender:調用 trigger() 方法的對象
yii\base\Event::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() 方法實現:
$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) { // ...發送 $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\db\BaseActiveRecord::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\db\BaseActiveRecord 或其子類的實例觸發 yii\db\BaseActiveRecord::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")。
給組件對象綁定事件處理函數
$component->attachEventHandler($name, $handler); $component->onBeginRequest = $handler ;
yii支持一個事件綁定多個回調函數,上述的兩個方法都會在已有的事件上增加新的回調函數,而不會覆蓋已有回調函數。
$handler即是一個PHP回調函數,關于回調函數的形式,本文的最后會附帶說明。如CLogRouter組件的init事件中,有以下代碼:
Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs'));
這就是給CApplication對象的onEndRequest綁定了CLogRouter::processLogs()回調函數。而CApplication組件確實存在名為onEndRequest的方法(即onEndRequest事件),它之中的代碼就是激活了相應的回調函數,即CLogRouter::processLogs()方法。所以從這里可以得出,日志的記錄其實是發生在CApplication組件的正常退出時。
在需要觸發事件的時候,直接激活組件的事件,即調用事件即可,如:比如CApplication組件的run方法中:
if($this->hasEventHandler('onBeginRequest')) $this->onBeginRequest(new CEvent($this));
這樣即觸發了事件處理函數。如果沒有第一行的判斷,那么在調試模式下(YII_DEBUG常量被定義為true),會拋出異常,而在非調試模式下(YII_DEBUG常量定義為false或沒有定義YII_DEBUG常量),則不會產生任何異常。
回調函數的形式:
普通全局函數(內置的或用戶自定義的)
call_user_func(‘print', $str);
類的靜態方法,使用數組形式傳遞
call_user_func(array(‘className', ‘print'), $str );
對象方法,使用數組形式傳遞
$obj = new className(); call_user_func(array($obj, ‘print'), $str );
匿名方法,類似javascript的匿名函數
call_user_func(function($i){echo $i++;},4);
或使用以下形式:
$s = function($i) { echo $i++; }; call_user_func($s,4);
關于PHP中Yii框架的event事件機制的原理是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。