整个应用是基于下面这句代码运行起来的:
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的地方之前,只要你提供了事件的实现并注册在之后的地方需要的时候即可调用。