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();