Yii2事件原理解析介绍了Yii2中事件分为三个等级:
(1)类级别事件,
(2)实例级别事件,
(3)全局事件。
下面写一个demo简单展示类与实例级别事件的绑定与触发的实现原理,加深理解。
1.demo类结构:
(1)Event类代表事件基类,存储了事件名称,触发者,处理状态等基本信息,同时提供一个静态私有变量$_events用于存储类级别的事件绑定情况,静态公有on方法用于绑定类级别事件,静态公有trigger方法用于触发类级别事件;
(2)Component类代表组件基类,这里只实现了与事件绑定与触发相关的部分属性与方法,私有成员变量$_events用于记录实例对象绑定的事件信息,on方法用于绑定实例级别事件,trigger方法用于触发实例级别事件;
(3)类A继承自Component,定义了两个事件handler,类B继承自A,定义了一个事件handler。
demo主要目的是理解以下内容:
(1)实例级别事件的实例隔离特性;
(2)实例事件与类级别事件的触发顺序;
(3)基类事件与子类事件的相互影响。
2.demo源码
(1)基础类实现
class Event{
public $name;
public $sender;
public $handled;
private static $_events = [];
public static function on($class,$name,$handler,$data=[],$append=true){
$class = ltrim($class,'\\');
if($append || empty(self::_events[$name][$class])){//注意这里从事件出发找到对应类的handlers
self::$_events[$name][$class][] = [$handler,$data];
}else{
array_unshift(self::$_events[$name][$class][],[$handler,$data]);
}
}
public static function trigger($class,$name,$event=null){
$class = is_object($class) ? get_class($class) : ltrim($class,'\\');
echo 'class event trigger:',$class,' event:',$name,' ';
if(empty($event)){
$event = new Event();
}
$event->name = $name;
$event->sender = empty($event->sender) ? $class : $event->sender;
//方法1)yii源码中使用spl的class_parents与class_implements找到类的所有父类与实现的接口
// $classes = array_merge(
// [$class],
// class_parents($class),
// class_implements($class)
// );
//方法2)这里使用反射机制实现同样功能
$classes = [$class];
$reflection = new \ReflectionClass($class);
$classes = array_merge($classes,$reflection->getInterfaceNames());
while($parent = $reflection->getParentClass()){
$parentName = $parent->getName();
$classes[] = $parentName;
$classes = array_merge($classes,$parent->getInterfaceNames());
$reflection = $parent;
}
foreach($classes as $c){
$handlers = self::$_events[$name][$c];
if(is_array($handlers)){
foreach($handlers as $handler){
call_user_func($handler[0],$event);
if($event->handled){
return;
}
}
}else{
echo $name,' has no handlers binded to class ',$c,'!<br>';
}
}
}
}
class Component{
private $_events;
public function on($event,$handler,$data=null,$append=true){
$this->_events[$event][] = [$handler,$data];
}
public function trigger($name,$event=null){
if(!isset($event)){
$event = new Event();
}
if(empty($event->sender)){
$event->sender = $this;
}
$event->name = $name;
//输出事件触发者
echo 'object event trigger:',$event->sender->name,', event:',$event->name,' ';
$handlers = $this->_events[$name];
if(is_array($handlers)){
foreach($handlers as $handler){
call_user_func($handler[0],$event);
if($event->handled){
return;
}
}
}else{
echo 'no handler binded to object ',$event->sender->name,'<br>';
}
Event::trigger($this,$name,$event);
}
}
class A extends Component{
public $name;
public function __construct($name){
$this->name = $name;
}
public function handlerA($event){
echo 'handler: handlerA!<br>';
}
public function handlerC($event){
echo 'handler: handlerC!<br>';
}
}
class B extends A{
public function handlerB($event){
echo 'handler: handlerB!<br>';
}
}
(2)验证我的问题
//给类A绑定一个事件EVENT_A
Event::on('A','EVENT_A',['A','handlerC']);
//给A的实例a绑定同一事件EVENT_A并触发
$a = new A('a');
$a->on('EVENT_A',[$a,'handlerA']);
$a->trigger('EVENT_A',new Event());
//A的实例c触发未绑定的实例事件EVENT_A
$c = new A('c');
$c->trigger('EVENT_A');
//给类B的实例b绑定另一个事件EVENT_B并触发EVENT_A与EVENT_B
$b = new B('b');
$b->on('EVENT_B',[$b,'handlerB']);
$b->trigger('EVENT_A');
$b->trigger('EVENT_B');
(3)演示结果
//当类与实例绑定了同一事件时,实例级别的handlers先执行,然后再执行类级别的handlers
object event trigger:a, event:EVENT_A handler: handlerA!
class event trigger:A event:EVENT_A handler: handlerC!
EVENT_A has no handlers binded to class Component!
//实例a与实例c均为A的实例,但c未绑定实例事件,所以实例事件存在实例隔离特性
object event trigger:c, event:EVENT_A no handler binded to object c
class event trigger:A event:EVENT_A handler: handlerC!
EVENT_A has no handlers binded to class Component!
//实例b是A的子类B的实例,虽然b与B均未绑定事件EVENT_A,但A存在对该事件的类级别处理,所以基类的事件绑定会影响子类
object event trigger:b, event:EVENT_A no handler binded to object b
class event trigger:B event:EVENT_A EVENT_A has no handlers binded to class B!
handler: handlerC!
EVENT_A has no handlers binded to class Component!
//子类B的类级别事件不会影响基类
object event trigger:b, event:EVENT_B handler: handlerB!
class event trigger:B event:EVENT_B EVENT_B has no handlers binded to class B!
EVENT_B has no handlers binded to class A!
EVENT_B has no handlers binded to class Component!