文档章节

yii2 - Event 实例及源码分析

big_cat
 big_cat
发布于 2016/05/27 14:29
字数 2177
阅读 270
收藏 7

yii2 可以方便的使用 Event 组件基类 来实现 注册事件和监听触发 机制,每个事件都有自身的事件队列

首先要知道,yii2的事件一共有三类:对象级,类级,全局 \Yii::$app 级

对象和 全局是通过 yii\base\Component 管理的

类级 是通过 yii\base\Event 管理的

yii\base\Object 

    |--yii\base\Behavior

    |--yii\base\Event

    |--yii\base\Component

        |--yii\base\Application

        |--yii\base\Controller

        ..............

而且要注意的一点是:对象的事件队列触发完毕后会隐式的检测对象所属的类是否也存在此事件的队列,若存在,则会使用 Event::trigger($this, $event_name, $event = null) 进行触发,$this 传递进去会被提取类名,进而使得管理类级别的 Event 也可以执行事件,看下源码就知道了

yii\base\Event 定义和实现了event的一些基础属性和类级别的方法。虽然在对象级的事件中处理时并没有直接调用 Event 中的类级别的事件方法,但传递给 handler 方法的 event 都是 Event 的实例。

在 yii\base\Component 中重写了事件的 on/trigger 将其转变为对象级别的方法。要注意,yii2 会始终检测此对象的类是否有注册相同的事件,若注册则同样会联动触发,此时虽然是触发类级别的事件,但触发事件的上下文依然是对象级别的,默认的 event->sender 依然是当前对象

    //触发的事件名称
    public $name;
    //触发的传递者 如果是对象级的事件则sender默认为此对象$this 如果是类级别的则默认为 null
    public $sender;
    //是否终端事件队列的执行
    //因为事件注册的 handler 方法可以接收 $event 对象 所以如果在内部将 $event->handled设为 true 后
    //事件队列将会终端不再向下执行
    public $handled = false;
    //用于传递的数据
    public $data;

以上为 Event 基类的属性,我们可以继承它来扩展我们自己想要的 $event 回调参数的字段


yii\base\Component 的 on 方法

我们常用的组件:控制器、模型等组件的事件都是继承于 Component 的

通过 on 方法向自身的 _events 队列数组注册事件,默认挂载到队列尾部,根据时序性执行,但你也可以传参 append 为 false 将此事件从队列头压入,会优先于当前已注册的事件执行

$name   事件名称

$hander 负责处理方法 此方法会接受 Event 的对象 我们可以将数据填充给此对象做到传参的目的

$handled 标注事件队列是否处理完成,true的话讲不会再执行队列中的剩余事件

$data 这里的data 最终会赋值给 $event->data 属性,可以传递一些参数,但注意他对标量并不会延迟绑定,比如你注册时传递 $this->name,则是以 name 属性的当前值存储的,后续 name 改变,触发时这个改变也无法传递给 data,这里传递 $this 对象本身比较靠谱,可以适当的提高灵活性

    /** 
     * @param string $name the event name
     * @param callable $handler the event handler
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
     * @param boolean $append whether to append new event handler to the end of the existing
     * handler list. If false, the new handler will be inserted at the beginning of the existing
     * handler list.
     * @see off()
     */
    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]);
        }
    }

yii\base\Component 的 trigger 方法

trigger 可以不传递 Event 参数,内部默认会给你 new 一个 yii\base\Event 对象,并默认将 sender 属性默认为当前调用触发的对象$this,当然我们也可以传递一个 Event 的实例来自定义设定。

    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);
    }

注意:最后的 Event::trigger($this, $name, $event),即我们之前说的此对象的类如果也有注册相同事件,则也会被触发,同时要注意这里是对象级别隐式调用事件,传递的 $class 为 $this,如果传递的 $event->sender属性为 null 的话在下面的执行中会被默认为 $this,嗯,也就像我们之前说的 对象触发默认 sender 为本对象,类触发默认 sender 为 null


yii\base\Event::on 方法

与 yii\base\Component::on 没有太大区别,都是管理一个事件的队列,这里只不过需要给出类名而已

yii\base\Event::trigger 方法

    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 如果是对象时则会被提取类名


使用方法

对象级别

yii\base\Component::on($event_name, $handler_method, $event_data = null,$is_append = true)
//注册 handler是匿名方法
$this->on("my_first_event", function($event) {
    // 事件名称:my_first_event
    echo $event->name;
    // 触发者:当前对象,我这里拿到它的类名
    echo $event->sender->className();
    // 传递的数据
    echo $event->data;
    // 如果传递了 stop 的信号
    if ($event->data == "stop") {
        // 本事件的队列将会终止 后续注册的事件也不会被触发执行了
        $event->handled = true;
    }
}, "data you can get with event->data");

// handler 是对象方法
$this->on("my_first_event", [$this, 'eventHandler'], "you can get with event->data");

// handler 是类方法
$this->on("my_first_event", ["app\handlers", 'eventHandler'], "you can get with event->data");

// append 设为false,直接挂载到事件队列头优先执行,默认 true 是挂载到事件队列尾步的
$this->on("my_first_event", function($event) {
    echo "though i am the last one, but i can execute firstly!"
}, "this is event data you can get with event->data", false);


//触发 默认第二个参数Event的实例为空
$this->trigger("my_first_event");

//自己定义一个 event
//要注意这里传递的 event 的 name data 参数是无法改变的,会在trigger 方法里自动配置
//handled 参数也不可能手动设定,handled 是在你的 handler 方法中来决定的 
$this->trigger("my_first_event", new Event(["sender" => "default is $this now i change it"]));

//传递的 event 必须是 Event 的实例,所以我们也可以通过 Event 的子类来扩展数据
$this->trigger("my_first_event", new MyEvent([
    "desc" => "sub class of Event and expand property",
    "title" => "i can pass some other data",
    "id" => "33"
]));

/** MyEvent
|class MyEvent extends \yii\base\Event
|{
|    public $desc;
|    public $title;
|    public $id;
|}
*/

类级别

yii\base\Event::on($class, $event_name, $handler_method, $event_data = null,$is_append = true)

其实和对象也没太大区别,只是将事件定义和触发提升到类的层次,需要手动指定是哪个类的事件而已

Event::on(__CLASS__, "my_first_event", [__CLASS__, 'classEventHandler'], "event->data");
//要注意这里如果不传递 event 的话,默认的 event->sender 为 null 对象级别的默认的是当前对象 $this
Event::trigger(__CLASS__, "my_first_event");
//给他一个sender
Event::trigger(__CLASS__, "my_first_event", new Event(['sender' => "this is a class"]));

public static funtion classEventHandler($event)
{
    echo $event->name . $event-> sender . $event->data;
    // if set handled is true the event queue will end and return
    $event->handled= true;
}

全局应用级别

\Yii::$app 是全局应用的句柄

\Yii::$app->on("can_be_trigger_anywhere", function($event) {
    echo "全局应用级别事件,可以在应用的任何地方注册或触发"
})
\Yii::$app->trigger("can_be_trigger_anywhere");

可以注册全局事件,可以在全局项目进行注册和触发


小结

 1、on 方法注册事件,可以附加数据,通过 handler 方法的 $event->data 接受,同时可以设定append来绝对事件是否被优先执行

2、trigger 方法传递的 event 默认为空时,如果为类级别的触发则 $event->sender 为 null,如果为对象级别的触发则 $event->sender 为当前对象 $this

3、每个事件都有一个自己的事件队列,而且,我们可以通过 设定 $event->handled 的状态来绝对本事件的处理队列是否继续下去

© 著作权归作者所有

big_cat
粉丝 49
博文 237
码字总数 162867
作品 0
长宁
后端工程师
私信 提问
yii2 - Behavior 实例及源码分析

Behavior 的简述 行为简单来说是组件的扩展,可以对组件的属性,方法,事件 (yii2组件的三大要点)进行扩展而无需改动组件现有的代码逻辑。即此行为所拥有的属性,方法,事件,都会被绑定它...

big_cat
2016/06/01
1K
0
Yii2 源码分析 - 入口文件执行流程

以 yii 2.0.14 高级版的 frontend 为例,从 frontend/web/index.php 开始 入口文件看着就这么几行,简单的很,那他是怎么通过这几行来运行应用的呢?先看 Yii.php 内的逻辑 接下来,就是重头...

botkenni
2018/05/03
30
0
yii2源码分析之执行基本流程

用yii2框架用了将近2年,一直都没有去看过它底层源码, 马上快不用了,最近对其源码研究一番,哈哈 废话少说,上代码, 入口文件是web/index.php

china_lx1
2018/04/22
0
0
yii2 - Property 之 默认构造方法 和 setter/getter 方法

yii2 内部规定了 construct 函数的构造形式,以键值对儿数组作为参数,进行属性的初始化,但要注意给属性赋值的工作是转交给基类 yiibaseYii::configure 方法的,故无法直接访问本类的私有属...

big_cat
2016/05/27
788
0
yii2源码分析之组件实例化流程

读本篇文章,建议先看看我之前的文章php依赖注入 到此,现在我们正式开始分析yii2框架组件构造流程 我们先从yiidiServiceLocator(服务定位器)入手吧!!让我们先看个实例: use yiidiServiceLoc...

china_lx1
2018/06/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JS--function

一、声明提前(hoist) 在js程序开始执行前,引擎会查找所有var声明的变量和function声明的函数,集中到当前作用域顶部集中创建,赋值留在原地 二、三种创建函数的方式 1、声明方式创建函数-...

wytao1995
今天
4
0
微服务之间调用控制器注解类型的差异

今天在一个业务服务通过Feign调用文件服务上传文件时遇到了几个问题: 1. 提示http请求头过大的问题; 此时需要修改bootstrap.yml,加入 server: max-http-header-size: 10000000 用以放大...

不再熬夜
今天
7
0
用 4G 工作是什么体验

七月开始,因为工作原因,在公司附近租了个住处,方便工作。离公司近了,感觉就是不一样,之前每天 5:30 就要起床赶地铁,现在可以睡到自然醒,一看才 7 点,悠闲的起床洗漱,踱步到公司,都...

zzxworld
今天
6
0
sonar报错volatile

问题发生 原先代码如下: //认证授权码private static volatile String AUTHORIZATION_CODE = "init"; git push 之后,sonar认为这是个bug检测报告截图如下: 分析排查 解释说明: Markin...

开源小菜鸟2333
今天
5
0
《Java实践指南》--读后

闲读《Java实践指南》... 1.lvy 某些项目中能够看到ivy.xml。早期使用ant的项目中,常常用ivy.xml来下载项目依赖。 2.ant 作为java程序员,应该都知道ant,虽然可能用过的人不多。为什么ant...

RippleChan
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部