说起事件(event),我们可是一点都不陌生。现实生活当中的事件无处不在,比如你发了一条微博,触发了一条事件,导致关注你的人收到了一条消息,看到你发的内容;比如你通过支付宝买东西,付了款,触发一个事件,导致你收到一条短信,告诉你刚刚扣款了,你账户余额还有多少…
我们将事件稍稍加以抽象,发现事件具有某些共同特点,比如事件其实不是孤立存在,它只是某个流程或者工序的一个特殊的“点”,可以理解为时间点,也可以理解为逻辑的点;其次,事件上可以绑定一些“动作”,比如发送一条短信或者发送一个邮件;第三,我可以绑定,当然也可以解绑,如果我反感频频的短信提醒,我可以选择不发短信,我自己去查看账户余额;第四,这些动作和主流程往往并没有直接的关系,往往是“附加”的:我已经付完款了,你发短信或者不发,发邮件或者直接客服通知我其实影响不大,并不影响我购物这个行为本身——反正我已经付完款,预期不久就会收到商品了。
其实,说到这里,已经有点入戏的感觉了。人有生老病死,一年有春夏秋冬四季演替,封建王朝有兴盛、停滞、衰亡的周期律——“其兴也勃焉,其亡也忽焉”。换句话说,人,季节,王朝等等这些世间万物都有自己的生命周期。
那么在软件行业,一个系统,一个组件,一个功能,一个类都是有自己的生命周期的——创建、运行、销毁。比如一个类(Class)都要经过__construct()
,调用各个类方法,__destruct() 的过程。每个程序的运行,要理解为一个过程或者流程。那么这样来理解事件就有意义了:事件无非就是这个过程之中一些有意义的“点”。这些点是人为做的设定,比如插入数据库数据,那么校验前、后,插入前、后就可能是几个有意义的时间节点,把这些节点看成一个个的事件,就更加便于我们去理解这整个过程。复杂的东西理解起来困难,我们分成若干个阶段来理解岂不是就简单许多了吗?想想为啥计算机网络为啥要分成物理层、数据链路层、网络层,运输层、应用层这个简单道理就行了。除了我们更好的去理解程序的运行流程,更为重要的是还能够使我们能够“介入”这个流程,改变这个流程,从而实现我们的目的。这就是往事件上“附加”一些动作或者行为了,专业点说,就是事件处理器或者事件监听者。我们想要在某个特定的时间点做点什么,就事先在这个对应的事件上绑定事件处理器,当流程走到这一步时,相应的处理器就被执行,完成我们事先设定的目的。想象一下:软件的运行就是沿着自己设定的路线,走过一个又一个重要节点,同时触发这些节点事件,最终走到自己生命终点的一个过程。将事件理解为流程中的节点,不仅可以帮我更好的认识程序,也能更好的帮助我们改造程序。
事件的实现,其实是观察者模式的一种体现。可能有人会说,为啥是观察者模式?Yii的事件并不符合观察者模式的经典定义啊?的确,Yii的事件实现机制中确实不存在经典Observer和Subject即观察者和被观察者。但是,不要在意这些细节——观察者模式主要面临场景是一对多松耦合的对象之间的关系,要解决的问题就是对象状态改变,其他对象能得到通知。“一对多”和“松耦合”是其核心要义和功能。看看Yii的事件其实正好能实现这两个功能:Yii的每个事件(beforeSave,afterSave这些)都是一个被观察者,每个事件处理器都是它的观察者,被观察者被触发时,所有观察者都依次要执行。
小编心得:观察者模式的经典定义——观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。
下面,我们来谈谈Yii2本身是如何实现事件的。
事件相关的类
在Yii中,事件是Component引入的,BaseObject 是不支持事件的,BaseObject只支持属性。当你需要使用事件时,就需要继承Component或者它的子类,而不能直接继承BaseObject。好在我们之前反复提过,Yii2是基于Component的,就是说,Yii2中大部分类都是Component的子类,是天生带有事件功能的。和其他框架相比,我觉这是Yii2的一大优势——从“原子”层面都已经让类具备很强的扩展性了。面对具体业务场景,事件往往成为一把利剑,使得使用者得心应手。同时,\yii\base\Event是于Yii2事件功能紧密相连的一个类,他封装与事件相关的有关数据,并可以自定义一些功能函数作为辅助。
class Event extends BaseObject
{
// 事件的名称
public $name;
// 事件发布者,通常是调用了 trigger() 的对象或类,也可传递别的对象。
public $sender;
// 此事件是否被处理了,若为true,那事件的处理到此为止
public $handled = false;
// 事件的相关数据
public $data;
// 保存全局事件的处理器
private static $_events = [];
// 绑定类级别事件handler
public static function on($class, $name, $handler, $data = null, $append = true)
{
// ...
}
// 取消类级别事件handler的绑定
public static function off($class, $name, $handler = null)
{
// ...
}
// 解绑所有类级别事件的handler
public static function offAll()
{
self::$_events = [];
}
// 判断一个类和其所有父类上是否绑定有事件处理器
public static function hasHandlers($class, $name)
{
// ...
}
// 触发类级别的事件,执行所有这个事件上绑定的handler
public static function trigger($class, $name, $event = null)
{
// ...
}
}
事件处理器
所谓事件处理器(事件handler)就是事件处理程序。“触发事件”和执行“事件处理器”其实是一个意思。说到底,事件处理器到底是什么呢?其实仅仅只是一个callable——或者是全局函数,或者是类的方法等。本质上说,事件处理器就是一段PHP代码,一个PHP函数。
具体说来,handler 有这么几种形式:
匿名函数
function ($event) { ... }
对象的方法
$object->handleClick() 即 [$object, 'handleClick']
类的静态方法
Page::handleClick()
PHP系统函数或者自定义全局函数 trim,array_filter,handleClick等,注意只是函数名,不需要带任何参数
无论是哪种形式的handler,它们最终都必须要有如下的形式:
function ($event){
...}
这里的$event正是yii\base\Event类或者其子类的实例。
事件的绑定和解绑
有了事件处理器,就可以将其绑定到特定的事件上了。绑定事件处理器是通过yii\base\Component::on() 来进行,相应的解绑是yii\base\Component::off()。
绑定
// 绑定的过程就是将handler写入$_event[]的过程
public function on($name, $handler, $data = null, $append = true)
{
$this->ensureBehaviors();
//$append为true时$handler置于数组最后,触发时最后被执行
if ($append || empty($this->_events[$name])) {
$this->_events[$name][] = [$handler, $data];
} else {