众所周知,yii的三大特性是:属性、事件、行为,上一篇博文简单讲解了yii中的属性,本文接着讲讲yii的事件。

  事件是代码解耦的一种方式,设计业务流程的一种模式。在yii2.0中,通过Yii\base\Component继承yii\base\Object,重载__get()、__set()方法,引入了事件和行为,使得开发变得十分方便。然而,在方便开发的同时也牺牲了一定的效率,所以若不需要使用事件和行为,可不必继承Component而选择继承Object,Object的效率更接近原生的PHP类。

  首先说说yii事件的使用,由于Yii\base\Component类已经实现了事件,所以只要某个类继承了Component类,它的对象就可以调用on()方法来绑定事件,然后在需要的地方调用trigger()方法触发指定事件。下面举一个简单的例子来说明。

  例如,在博客系统中,博文数据表中有created_at(发表时间)和updated_at(更新时间)两个字段,现在我想通过事件来在更新数据表之前给这两个字段赋值。我们知道,yii的AR模型在数据表插入或更新数据之前会调用beforeSave()方法,现在就在博文AR模型中重写这个方法去绑定并触发事件给这两个字段赋值,代码如下:

public function beforeSave($insert) {
if(parent::beforeSave($insert)) {
$this->on('haha', [$this, 'setTime'], $insert);
$this->trigger('haha');
return true;
}
return false;
}

(这个例子中,事件的绑定和触发在同一个地方进行,这里使用事件意义不大,只是为了举个简单例子来说明yii事件的使用而已哈)

  以上代码将[$this, ‘setTime’]这个处理器绑定在名为’haha’的事件上,这个处理器其实就是博文AR模型的setTime()方法,第三个参数表示是否插入数据,将会在事件触发的时候传递给setTime()方法。setTime()方法代码如下:

public function setTime($event) {
if($event->data) {
$this->uid = User::getUid();
$this->created_at = time();
}
$this->updated_at = time();
}

其中$event->data就是on()函数的第三个参数了。setTime方法的参数$event在yii中是yii\base\Event类的对象,若在事件触发之时需要传递一些参数给处理器函数,可以写一个子类继承yii\base\Event类,设置一些成员变量,然后创建该类的一个对象,把需要传递的参数赋值给这个对象的成员变量,接着把这个对象赋值给事件触发方法trigger()的第二个参数,在处理器函数中就可以接收到这些参数值了。

  没错,在yii中使用事件就是这么简单!可以看到,其实事件的本质就是把一段代码抽出来单独写成一个方法,然后把它绑定在某个事件上,最后在需要调用这个方法的地方触发这个事件就ok了。个人理解,设计事件主要是为了代码解耦与重用吧。

  Yii的事件到底是怎么实现的呢?说到底其实就是一个数组和三个方法就能搞定的事情,且听我细细道来。首先,使用一个数组(下文称为事件队列)来保存所有的事件,数组的键为各个事件名称,数组的元素值则是各个事件触发时需要调用的函数以及需要传递给函数的参数(下文称为事件处理器),可以有一个或多个,所以也是使用数组来存储,下文称为处理器队列,一个事件对应一个处理器队列。为一个事件绑定一个事件处理器则是在事件队列中找到这个事件对应的处理器队列并添加一个处理器,把一个处理器从某个事件解绑则是在事件队列中找到这个事件指定的处理器并从它的处理器队列中删除,触发一个事件则是在事件队列中找到这个事件然后按顺序调用它对应的处理器队列中的所有处理器。

  为了方便理解,下面上图说明(事件队列数据结构图):

  好了,理解了事件的大概实现逻辑之后,让我们从Yii\base\Component类的源码来看看具体的实现细节,我根据自己的理解在代码适当位置中加上了一些注释:

/**
*
* @var 存储事件列表的数组,形式:事件名称 => 对应的事件处理器列表
*/
private $_events = []; /**
* 为某个事件绑定一个事件处理器
* @param $name:事件名称,字符串形式
* @param $handler:事件处理器,指定事件触发时调用的函数,有4种形式:
* 1.全局php函数名,字符串形式
* 2.[类名, 方法名],数组形式
* 3.[对象, 方法名],数组形式
* 4.匿名函数,形式:function($event){ ... }
* @param $data:事件触发时传递给事件处理器函数的参数,在事件处理器函数调用形式:$event->data
* @param $append:为true时表示将绑定的事件处理器添加在事件处理器列表的最后,为false则添加在最前面
*/
public function on($name, $handler, $data = null, $append = true)
{
$this->ensureBehaviors();
if ($append || empty($this->_events[$name])) {//添加到列表后面
$this->_events[$name][] = [$handler, $data];
} else {//添加到列表前面
array_unshift($this->_events[$name], [$handler, $data]);
}
} /**
* 解绑事件处理器
* @param $name:事件名称
* @param $handler:要解绑的事件处理器,为null表示解绑这个事件的所有处理器,即删除整个事件
* @return boolean
*/
public function off($name, $handler = null)
{
$this->ensureBehaviors();
if (empty($this->_events[$name])) {//事件不存在
return false;
}
if ($handler === null) {//删除事件
unset($this->_events[$name]);
return true;
} $removed = false;
foreach ($this->_events[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);//删除指定的事件处理器
$removed = true;
}
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
}
return $removed;
} /**
* 触发一个事件
* @param $name:事件名称
* @param $event:\yii\base\Event类对象,作为传递给事件处理器的参数
* @return type
*/
public function trigger($name, Event $event = null)
{
$this->ensureBehaviors();
if (!empty($this->_events[$name])) {
if ($event === null) {//没有$event参数则创建一个默认对象
$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];//这里把on()方法绑定事件处理器时传递的$data参数传递给事件处理器
call_user_func($handler[0], $event);//调用事件处理器方法
if ($event->handled) {//若在某个事件处理器中将$event->handled置为true,表示事件处理完毕,后面的处理器不再被调用
return;
}
}
}
//触发类级别事件
Event::trigger($this, $name, $event);
}

  理解了事件的实现原理之后,我们就会发现,其实我们自己也可以实现事件的!

额,说了这么多也不知道说清楚了没有,反正我自己觉得挺清楚的,哈哈。。。

Yii2之事件的更多相关文章

  1. PHP观察者模式与Yii2.0事件

    1.先看PHP观察者模式的实现: 想要使用事件.必须实现事件的基类.统一的addObserver和trigger方法 定义统一接口.所有的观察者都要实现此接口 //事件的基类 abstract cla ...

  2. 学习yii2.0——事件

    参考:https://www.yiichina.com/doc/guide/2.0/concept-events 事件 yii框架中的事件定义和JavaScript中的事件定义差不多:为某个事件绑定一 ...

  3. Yii2的深入学习--yii\base\Event 类

    根据之前一篇文章,我们知道 Yii2 的事件分两类,一是类级别的事件,二是实例级别的事件.类级别的事件是基于 yii\base\Event 实现,实例级别的事件是基于 yii\base\Compone ...

  4. yii2 队列 shmilyzxt/yii2-queue 简介

    在yii2论坛中看到一个关于队列的帖子,感觉不错.http://www.yiichina.com/extension/1084 (注:SendMail 错写为 SendMial,粘贴时要注意了.) 在 ...

  5. 搭建自己的PHP框架心得(二)

    h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h ...

  6. JNI详解---从不懂到理解

    转载:https://blog.csdn.net/hui12581/article/details/44832651 Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 C ...

  7. Yii2的深入学习--事件Event

    我们先来看下事件在 Yii2 中的使用,如下内容摘自 Yii2中文文档 事件可以将自定义代码“注入”到现有代码中的特定执行点.附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行.例如, ...

  8. Yii2事件

    namespace app\components; use yii\base\Component; use yii\base\Event; class MessageEvent extends Eve ...

  9. Yii2基本概念之——事件(Event)

    说起事件(event),我们可是一点都不陌生.现实生活当中的事件无处不在,比如你发了一条微博,触发了一条事件,导致关注你的人收到了一条消息,看到你发的内容:比如你通过支付宝买东西,付了款,触发一个事件 ...

随机推荐

  1. 教你在Java接口中定义方法

    基本上所有的Java教程都会告诉我们Java接口的方法都是public.abstract类型的,没有方法体的. 但是在JDK8里面,你是可以突破这个界限的哦. 假设我们现在有一个接口:TimeClie ...

  2. Mybatis学习(三)XML配置文件之mybatis-config.xml

    1.MyBatis的配置文件结构 1.1 properties 这些是外部化的,可替代的属性,这些属性也可以配置在典型的 Java 属性配置文件中,或者通过 properties 元素的子元素来传递. ...

  3. [UIKit学习]07.关于如何选择UIButton、UILable、UIImageView

    如何选择UIButton.UILable.UIImageView 在不添加手势的前提下,只要不涉及到点击和多状态表现就尽量不要选择UIButton

  4. NopCommerce添加事务机制

    NopCommerce现在最新版是3.9,不过依然没有事务机制.作为一个商城,我觉得事务也还是很有必要的.以下事务代码以3.9版本作为参考: 首先,IDbContext接口继承IDisposable接 ...

  5. PuTsangTo-单撸游戏开发02 测试场景与单轴移动

    且不说立项与设计阶段的工作量,一个完整的游戏在开发阶段设计的职责范围也是很广,还有个大问题就是PuTsangTo项目也是本人在边学边做,截止目前还是满满的无从下手的感觉,一方面是技能与经验不足,另一方 ...

  6. Opengl4.5 中文手册—F

    索引 A      B    C      D     E     F     G H      I     J      K     L     M     N O      P    Q      ...

  7. css常用属性2

    1  浮动和清除浮动 在上篇的第十一节--定位中说道: CSS 有三种基本的定位机制:普通流.浮动和绝对定位. 普通流和绝对定位已经说完,接下来就是浮动了. 什么是浮动? CSS 的 Float(浮动 ...

  8. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  9. 转载 iOS拦截导航栏返回按钮事件的正确方式

    原文链接:http://www.jianshu.com/p/25fd027916fa 当我们使用了系统的导航栏时,默认点击返回按钮是 pop 回上一个界面.但是在有时候,我们需要在点击导航栏的返回按钮 ...

  10. netty4.x 传输文件

    一:简介 netty传输文件的例子并不多,当前的项目刚才需要使用netty,所以就记录一下使用方法,使用netty传输文件,首先需要启动一个服务端,等待服务端请求监听,然后传输文件的时候,启动一个客户 ...