整個應用是基于下面這句代碼運行起來的:
Yii::createWebApplication($env->configWeb)->run();
是的,就是這個run方法。我們在CWebApplication中找到run方法的實現:
public function run(){
//應用跑起來之前
if($this->hasEventHandler('onBeginRequest'))
$this->onBeginRequest(new CEvent($this));
//最后運行的方法 -- 1
register_shutdown_function(array($this,'end'),0,false);
//處理請求
$this->processRequest();
//處理請求結束之后 -- 2(2的運行順序在1之前)
if($this->hasEventHandler('onEndRequest'))
$this->onEndRequest(new CEvent($this));
}
接下來我們應該來說明這句代碼:
$this->hasEventHandler('onBeginRequest')
不過在解釋它之前,我們需要理解一下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)));
}
2 . 給組件對象綁定事件處理函數
$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);
總結: 關于Yii的事件機制其實就是提供了一種用于解耦的方式,在需要調用event的地方之前,只要你提供了事件的實現并注冊在之后的地方需要的時候即可調用。