虽然PHP语言本身不能实现异步事件,但是并不代表事件在PHP中不是
一个非常有效的代码注入方式。TinkSNS中就有钩子,允许用户在不修
改主体业务代码的时候执行不同的操作。钩子对于一些活动,比如充
值送积分,充值打折扣。不同的活动,不一样,如果每次都要修改充
值业务的主体代码,不仅麻烦,而且容易产生bug。因此可以在充值成
功之后触发充值成功的事件,在事件处理中去处理相应的返现,优惠打
折操作。
看一下Event类的主体结构
class Event extends Object{
public $name;
public $sender;
public $handled = false;
private static $_events = [];
public static function on($class, $name, $handler, $data = null, $append = true) {
}
public static function off($class, $name, $handler = null) {
}
public static function offAll() {}
public static function hasHandlers($class, $name) {
}
public static function trigger($class, $name, $event = null) {
}
}
整个类非常简单。事件绑定,事件解绑,解绑所有事件,是否绑定事件,触发事件五个函数,实现
事件处理机制。所有通过静态方法绑定的事件都存储在静态变量_events中。
1.事件绑定
public static function on($class, $name, $handler, $data = null, $append = true) {
$class = ltrim($class, '\\');
if ($append || empty(self::$_events[$name][$class])) {
self::$_events[$name][$class][] = [$handler, $data];
} else {
array_unshift(self::$_events[$name][$class], [$handler, $data]);
}
}
事件通过调用Event::on($class,$eventName,$handler,$data,$append)
进行绑定。在event内部,相同类名,
相同事件名的事件handler和数据data放入数组中。根据$append的值添加到事件数组最末尾或最前面。
2.事件解绑
public static function off($class, $name, $handler = null) {
$class = ltrim($class, '\\');
if (empty(self::$_events[$name][$class])) {
return false;
}
if ($handler === null) {
unset(self::$_events[$name][$class]);
return true;
} else {
$removed = false;
foreach (self::$_events[$name][$class] as $i => $event) {
if ($event[0] === $handler) {
unset(self::$_events[$name][$class][$i]);
$removed = true;
}
}
if ($removed) {
self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
}
return $removed;
}
}
事件通过调用Event::off($class,$eventName,$handler)
进行事件处理函数解绑。
通过handler的值解绑类的某个事件处理,删除在$_events中的数据。如果事件不存在,则返回false.如果handler
是空的,则解除这个类这个事件的所有事件处理函数。如果事件handler不为空,则删除指定的handler,并重新生
成事件handler数组(事件执行需要循环)。
3.事件触发
public static function trigger($class, $name, $event = null) {
if (empty(self::$_events[$name])) {
return;
}
if ($event === null) {
$event = new static;
}
$event->handled = false;
$event->name = $name;
if (is_object($class)) {
if ($event->sender === null) {
$event->sender = $class;
}
$class = get_class($class);
} else {
$class = ltrim($class, '\\');
}
$classes = array_merge(
[$class],
class_parents($class, true),
class_implements($class, true)
);
foreach ($classes as $class) {
if (!empty(self::$_events[$name][$class])) {
foreach (self::$_events[$name][$class] as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
if ($event->handled) {
return;
}
}
}
}
}
在业务中,调用Event::trigger($class,$eventName,$event)
,触发事件,执行事件处理函数。
从_events数组中找到绑定改事件名的所有类,获取传入$class的类名,所有父类的类名,实现接口的
类名。判断这些类名是否在绑定改事件的类数组中,如果在则获取handler数组,调用call_user_func
执行。事件handler中,可以设置event->handled的值去停止执行后续事件处理函数。
从上面的触发机制可以看出来,子类触发事件,会触发父类的事件。因此在事件定义中,尽量把事
件安排在子类中
。同时,如果需要传入在事件处理中传入参数,需要在事件绑定中传入data,不能通
过$event传入.
Yii中的事件分三个级别。一个是由Event绑定触发的类级别事件。还有就是由Commponent绑定的对象级别,
还有Yii::$app绑定触发的全局级别事件。
class Handler{
public static afterPay($event){
}
}
class Recharge{
$chargeMoney;
$user;
public function pay(){
//to-do pay for charge
if($res)
{
Event::on(get_class($this),"AFTER_PAY",[Handler,afterPay],['money'=>$chargeMoney,'user'=>$user]);
Event::trigger(get_class($this),"AFTER_PAY");
}
}
}
class Recharge extends Component{
public function pay(){
//to-do pay for charge
if($res)
{
$this->on("AFTER_PAY",[Handler,afterPay],['money'=>$chargeMoney,'user'=>$user]);
$this->trigger("AFTER_PAY");
}
}
}
Component 事件触发函数
public function trigger($name, Event $event = null)
{
$this->ensureBehaviors();
if (!empty($this->_events[$name])) {
if ($event === null) {
$event = new Event;
}
if ($event->sender === null) {
$event->sender = $this;
}
$event->handled = false;
$event->name = $name;
foreach ($this->_events[$name] as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
// stop further handling if the event is handled
if ($event->handled) {
return;
}
}
}
// invoke class-level attached handlers
Event::trigger($this, $name, $event);
}
可以看出,Component事件触发跟Event的触发类似,重点在触发函数设置sender。类级别的sender是类名
对象级别的sender是对象。