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!