Event
Yii中的Event由两部分组成,分别是$events和EventHandler,其中$event代表事件发生时的具体数据,而EventHandler代表事件发生时的具体的处理函数。
因此,当错误发生时,直接调用onError函数就可以,这时,之前被注册过的onError这个事件对应的错误处理函数都会被执行。具体的ErrorHandler发生过程可以通过raiseEvent()的源代码来了解。
在Yii中,Event通常是在CComponent的子类中扩展出来的,一般以on开头,如:
public function onError($event)
{
$this->raiseEvent('onError', $event);
}
public function raiseEvent($name,$event) { $name=strtolower($name); if(isset($this->_e[$name])) { foreach($this->_e[$name] as $handler) { if(is_string($handler)) call_user_func($handler,$event); else if(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); else if(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; } } else if(YII_DEBUG && !$this->hasEvent($name)) throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.', array('{class}'=>get_class($this), '{event}'=>$name))); }
此函数的主要内容就是在CComponent的变量$_e寻找与事件名称(如本例中的'onError')对应的EventHandler(函数),然后调用该EventHandler,同时将事件发生时的数据作为参数传递给该函数,完成事件的处理。
那么既然有EventHandler的调用,那么肯定就会有Event和EventHandler的注册了,否则就没有EventHandler可调用。
说到注册,自然会想到CComponent类中的__set()函数了,来看一下__set()函数的源码:
public function __set($name,$value) { $setter='set'.$name; if(method_exists($this,$setter)) return $this->$setter($value); else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) { // duplicating getEventHandlers() here for performance $name=strtolower($name); if(!isset($this->_e[$name])) $this->_e[$name]=new CList; return $this->_e[$name]->add($value); } else if(is_array($this->_m)) { foreach($this->_m as $object) { if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name))) return $object->$name=$value; } } if(method_exists($this,'get'.$name)) throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.', array('{class}'=>get_class($this), '{property}'=>$name))); else throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.', array('{class}'=>get_class($this), '{property}'=>$name))); }
关于Event事件的注册是从第6行开始,从代码中可以看出Event名称必须以'on'开头,如onError,onClick等。对应的$value是一个List,即可以有多个EventHandler。
具体的注册代码,Yii注释中也已经给出:
$component->onClick=$callback; // or $component->onClick->add($callback);
总结一下,Event的原理就是首先要定义一个事件处理函数,其次将其注册到该类的对应的事件中去,最后在发生事件时调用该事件的EventHandler。实例如下,当改变类中的变量name时,打印字符串:
class example extends CComponent { private $name; public function getName() { return isset($name) ? $name : ''; } public function setName($newName) { $this->name = $newName; $this->raiseEvent('onChange', new CEvent()); } } //在某一controller中实现: $exa = new example(); $exa->onChange = array($this, "showChange"); $exa->setName("zhangsan"); public function showChange() { echo "changed"; }
Behavior
Behavior是一种跟EventHandler功能比较类似的另一种表现形式,有点类似于多重继承,通过类绑定的方法,component将一个或者多个CBehavior类的成员方法和变量绑定到自己身上。
通过调用CComponet中的attachBehavior方法实现关联,我们深入起源码看看究竟做了什么?
public function attachBehavior($name,$behavior) //$name:行为名称 $behavior:行为对象 { if(!($behavior instanceof IBehavior)) $behavior=Yii::createComponent($behavior); $behavior->setEnabled(true); $behavior->attach($this); return $this->_m[$name]=$behavior; }
程序很短,就是判断传入的behaviour是不是IBehavior的实例,根据情况做一下处理,反正最后是通过调用IBehavior的attach方法去挂到当前的CComponent方法。
好了,关键的地方来了。注意看第七行,没有错,起把传入的behavior传入了 $this->_m[$name]变量,这个也就是为什么可以多绑定的原因,实现了收集的机制。转入IBehavior看attach方法定义
public function attach($owner) { $this->_owner=$owner; foreach($this->events() as $event=>$handler) $owner->attachEventHandler($event,array($this,$handler)); }
分析这段代码,首先可以看出对于每一个Behavior,都只能属于一个Component.
那么Event和Behavior又怎么会有关系了?看到 $owner->attachEventHandler($event,array($this,$handler));这句了吗?$owner就是我们绑定behavior的component,用该方法的名称就可以很容易的发现这里实现的Event Handler的绑定功能。
这里的$this->events()是什么?
public function events() { return array(); }
怎么突然和上面匹配不了了?返回了一个空的array()?千万别搞错,这个函数是一个virtual的函数是需要重写的。你其实这里就可以理解为events()方法返回的是一个含有事件名称和事件处理方法的关联数组。
如果还不能理解,我们看下CModelBehavior的这段的具体实现,他可是继承自CBehavior的。
public function events() { return array( 'onAfterConstruct'=>'afterConstruct', 'onBeforeValidate'=>'beforeValidate', 'onAfterValidate'=>'afterValidate', ); }
看到了吗?定义了三个事件,以及相应的事件处理函数,只是这些事件处理函数,在你扩展的时候,需要重写。
总结一下,behavior其实与event很类似,只不过behavior是将事件分类整理到一起,归到一个类中,然后一起注册给component,因此behavior可以看做是一个事件与事件处理函数的集合。需要注意的是,当一个behavior被attach到一个component时,该behavior的所有函数都能被component调用。具体事例如下:
首先重写Behavior类:
class exampleBehavior extends CBehavior { //重写关联数组 public function events() { return array( 'onBegin' => 'begin'; 'onEnd' => 'end'; 'onClick' => 'click'; ); } //事件处理函数 public function begin() { } public function end() { } public function click() { } } //在某一个controller中实现: $exBehavior = new exampleBehavior(); $component->attachBehavior('example', 'application.behavior.exampleBehavior'); $component->begin();