首先需要明确的几个问题:

Q1、什么是事件?

A:事件就是一个有名字的行为。当这个行为发生的时候,称这个事件被触发。

Q2、监听器又是什么?

A:监听器决定了事件的逻辑表达,由事件触发。监听器和事件往往是成对的,当然也可以是一个事件对应多个监听器。监听器是对事件的反应。当事件被触发时,由监听器做出反应。这样一来,多个事件的触发可以导致一个监听器做出反应。一个事件也可以有多个监听器做出反应。(一句话:监听器和事件之间的关系既可以是一对多,也可以是多对一)

Q3、事件管理器又是干嘛的?

A:事件管理器(EventManager),从名字上就可以看出来是管理事件用的。但他怎么管理呢?事件管理器往往会为多个事件聚合多个监听器(这里的事件和监听器都是不定数【就是可以是一个也可以是多个】)。事件管理器还负责触发事件。

  一般来说我们用对象来表示事件。一个事件对象描述了事件的基本元素,包括何时以及如何触发这个事件。

  关于事件的基本元素:事件名称、target(触发事件的对象,一般是事件对象本身)、事件参数。之前我们讲过事件相当与一个行为,在程序里面我们经常使用方法或函数来表示行为。因此事件的参数往往也是函数的参数。

  另外关于Shared managers: 之前讲过一个事件可以针对多个监听器。这就是通过Shared managers实现的。EventManager的实现包含(组合)了SharedEventManagerInterface【在构造函数或者setSharedManager里面使用了代码注入的方式,详情可以查看源码】),而SharedEventManagerInterface描述了一个聚合监听器的对象,这些监听器只连接到拥有指定识别符的事件。SharedEventManager并不会触发事件,他只提供监听器并连接到事件。EventManger通过查询SharedEventMangaer来获取具有特定标识符的监听器。

  EventManager里面几个重要的行为:

1、创建事件:创建事件实际上只是创建EventManagerInterface的一个实例

2、触发事件:一般在事件行为里面使用trigger触发,这样我们执行该行为的时候便可以直接触发该事件。函数原型:trigger($eventName,$target=null,$argv=[]);$eventName一般为时间行为名(常用__FUNCTION__代替),$target则为事件对象本身可用$this代替,$argv为传入事件的参数(一般为事件行为的参数)。

  当然事件触发方式不仅仅只有trigger一种,还有triggerUntil,triggerEvent,triggerEventUntil。从名字上我们就可以看出分两类:trigger和triggerEvent;trigger类只单纯的触发事件,不需要实现创建事件实例只需要一个事件名字就可以了,而trigger不仅触发事件还顺带着触发监听器,需要事件实例。而带有Until后缀的方法都需要一个回调函数,每一个监听器的结果都会传到该回调函数中,如果回调函数返回了一个true的bool值,EventManager必须使监听器短路。(关于短路见下文的短回路)

更多内容请查看官方API,或者EventMangerInterface的具体注释。

3、创建监听器并连接到事件

  监听器可以通过EventManager创建,也可以通过SharedEventManager创建。两者都是使用attach方法,但参数有点儿不一样。

  我们先看EventManager的方式:

方法原型:attach($eventName, callable $listener, $priority = 1)

  很简单,我们只需要事件名,还有一个可调用函数,最后是优先级默认为1(zend里面的自带事件的优先级多为负数,所以如果你想让自定义的监听器优先级比较高,直接赋值一个正数就行了。)

  可调用函数也就是我们的监听器。事件名有个特殊情况:“*”。这类似于正则匹配,将所有的事件都连接到本监听器中。

  我们现在看看SharedEventManager方式:

方法原型:attach($identifier, $eventName, callable $listener, $priority = 1);

  与之前唯一不同的地方多了个identifier参数。关于identifier的源码注释如下:

used to pull shared signals from SharedEventManagerInterface instance;

用来从SharedEventmanager实例中拉取分享信号。identifier是一个数组,按照我的理解:如果一个事件(注意SharedEventmanager无法创建事件的)定义了identifier,就意味着该事件是可共享的。让后SharedEventManger实例使用attach创建监听器的时候传入identifier参数。EventManager就可以使用identifier参数查询所有的监听器。

  令人困惑的是既然有了事件名,那就可以通过事件名来查询相关监听器,那为何还要多此一举的添加identifier属性?我考虑到的是事件继承问题:假设有一个事件类Foo包含一个事件行为act,SubFoo继承了Foo类并重写了里面的事件行为act。两个类都的事件行为都具有相同的事件名act。这时候如果通过事件名来查询监听器,显然会有冲突。这时候我们定义identifier[__CLASS__, get_class($this)],并在监听器中指定identifier为SubFoo,显然会匹配到SubFoo类中的事件行为act。

  以上我们通过SharedEventManager可以监听多个事件,另外我们还可以通过listener aggregates实现。通过Zend\EventManager\ListenerAggregateInterface,让一个类监听多个事件,连接一个或多个实例方法作为监听器。同样的该接口也定义了attach(EventManagerInterface $events)和detach(EventManagerInterface $events)。我们在attach的具体实现中使用EventManager的实例的方法attach监听到多个事件。

  1. use Zend\EventManager\EventInterface;
  2. use Zend\EventManager\EventManagerInterface;
  3. use Zend\EventManager\ListenerAggregateInterface;
  4. use Zend\Log\Logger;
  5.  
  6. class LogEvents implements ListenerAggregateInterface
  7. {
  8. private $listeners = [];
  9. private $log;
  10.  
  11. public function __construct(Logger $log)
  12. {
  13. $this->log = $log;
  14. }
  15.  
  16. public function attach(EventManagerInterface $events)
  17. {
  18. $this->listeners[] = $events->attach('do', [$this, 'log']);
  19. $this->listeners[] = $events->attach('doSomethingElse', [$this, 'log']);
  20. }
  21.  
  22. public function detach(EventCollection $events)
  23. {
  24. foreach ($this->listeners as $index => $listener) {
  25. $events->detach($listener);
  26. unset($this->listeners[$index]);
  27. }
  28. }
  29.  
  30. public function log(EventInterface $e)
  31. {
  32. $event = $e->getName();
  33. $params = $e->getParams();
  34. $this->log->info(sprintf('%s: %s', $event, json_encode($params)));
  35. }
  36. }

使用Aggregate的好处:

1、允许你使用有状态的监听器

2、在单一的类中组合多个相近的监听器,并一次性连接他们

内省监听器返回的结果

  我们有了监听器,但如何接收他返回的结果呢?EventManager默认实现会返回一个ResponseCollection的实例。这个类继承于PHP的SplStack。基本结构是一个栈,所以允许你反序遍历Responses。

  ResponseCollection提供了有用的几个方法:

first():  获取第一个结果

last():  获取最后一个结果

contains($value):  查看是否栈里面是否包含某一个值,如果包含则返回true,否则false。

短回路监听器执行:

  什么叫短回路呢?假设你要做一件事情,直到这件事有了结果,这是一个回路。如果你提前知道了这件事的结果(比如之前做过这件事),那你就没比要把这件事完完全全的做完,这时候你只需要执行一个短回路。

  我们在添加EventManager的时候有一个缓存机制。在一个方法中触发一个事件,如果我们找到一个缓存结果就直接返回。如果找不到缓存结果,我们就将触发的事件缓存下来以备后用。实际上和计算机硬件里面的高速缓存一个道理。

  EventManager组件提供两种处理的方式:1、triggerUntil();2、triggerEventUntil。这两个方法都接受一个回调函数作为第一个参数。如果回调函数返回true,那执行停止。

  1. public function someExpensiveCall($criteria1, $criteria2)
  2. {
  3. $params = compact('criteria1', 'criteria2');
  4. $results = $this->getEventManager()->triggerUntil(
  5. function($r){
  6. return ($r instanceof SomeResultClass);
  7. },
  8. __FUNCTION__,
  9. $this,
  10. $params
  11. );
  12.  
  13. if($results->stopped()) {
  14. return $results->last()'
  15. }
  16. }

从上面范例中,我们知道,如果执行停止了很有可能是因为栈里面最后的结果满足我们的要求。这样一来,我们只要返回该结果,何必还要进行多余的计算呢?

  处理在事件中停止执行,我们还可以在监听器中停止执行。理由是我们曾经接收过某一个事件,现在我们又接收到了相同事件,理所当然的使用之前的结果就好了。这种情况下,监听器调用stopPropagation(true),然后EventManager会直接返回而不会继续通知额外的监听器。

  1. $events->attach('do', function($e) {
  2. $e->stopPropagation();
  3. return new SomeResultClass();
  4. });

当然,使用触发器范例可能会导致歧义,毕竟你并不知道最终的结果是否满足要求。

  Keeping it in order.

  偶尔你会关心监听器的执行顺序。我们通过监听器的优先级来控制执行顺序(上面说讲的短回路也会影响执行顺序)。每一个EventManager::attach()和SharedEventManager::attach()都会接受一个而外的参数:priority。默认情况下为1,我们可以省略该参数。如果你提供了该参数:高优先级执行的早,低优先级的可能会推迟执行。

  自定义事件对象:

  我们之前使用trigger()触发事件,在这同时我们也创建了事件。但trigger()的参数有限,我们只能指定事件的对象,参数,名称。实际上我们可以创建一个自定义事件,在Zendframework里面有个很重要的事件:MvcEvent。很显然MvcEvent便是一个自定义事件,该事件组合了application实例,路由器,路由匹配对象,请求和应答对象,视图模型还有结果。我们查看MvcEvent的源码会发现MvcEvent类实际上继承了Event类。同理我们的自定义事件对象也可以继承Event类或者继承MvcEvent。

  1. $event = new CustomEvent();
  2. $event->setName('foo');
  3. $event->setTarget($this);
  4. $event->setSomeKey($value);
  5.  
  6. //injected with event name and target:
  7. $events->triggerEvent($event);
  8.  
  9. //Use triggerEventUntil() for criteria-based short-circuiting:
  10. $results = $events->triggerEventUntil($callback, $event);

上面的代码可以看到我们使用自定义事件类创建了一个事件对象,调用相关拦截器为事件对象设置属性。我们有了事件对象还是用trigger()触发事件吗?显然不是,我们使用triggerEvent($event)方法,该方法接收一个事件对象。而triggerEventUntil有一个回调函数,该回调函数作为是否进行短回路的依据。

zendframework 事件管理(二)的更多相关文章

  1. zendframework 事件管理(一)

    zend里的事件管理器主要是为了实现: 1.观察者模式 2.面向切面设计 3.事件驱动构架 事件管理最基本的功能是将监听器与事件连接或断开.不论时连接还是断开都是通过shared collection ...

  2. Redis事件管理(二)

    Redis的定时器是自己实现的,不是很复杂.说说具体的实现吧. 定时器的存储维护采用的是普通的单向链表结构,具体节点定义为: /*时间定时器结构体*/ typedef struct aeTimeEve ...

  3. jquery技巧之让任何组件都支持类似DOM的事件管理

    本文介绍一个jquery的小技巧,能让任意组件对象都能支持类似DOM的事件管理,也就是说除了派发事件,添加或删除事件监听器,还能支持事件冒泡,阻止事件默认行为等等.在jquery的帮助下,使用这个方法 ...

  4. Google 和 Facebook 如何大规模处理 IT 事件管理 —— 2016 SRE 大会之我见

    [编者按]本文作者为 Maria Arbisman,主要介绍 Google 与 Facebook 两大巨头是如何大规模处理 IT 事件管理.文章系国内 ITOM 管理平台 OneAPM 编译呈现. 2 ...

  5. 基于JS的event-manage事件管理库(一步一步实现)

    关于文章 最近在提升个人技能的同时,决定把自己为数不多的沉淀记录下来,让自己理解的更加深刻,同时也欢迎各位看官指出不足之处. 随着node.js的盛行,引领着Javascript上天下地无所不能啊,本 ...

  6. 服务器文档下载zip格式 SQL Server SQL分页查询 C#过滤html标签 EF 延时加载与死锁 在JS方法中返回多个值的三种方法(转载) IEnumerable,ICollection,IList接口问题 不吹不擂,你想要的Python面试都在这里了【315+道题】 基于mvc三层架构和ajax技术实现最简单的文件上传 事件管理

    服务器文档下载zip格式   刚好这次项目中遇到了这个东西,就来弄一下,挺简单的,但是前台调用的时候弄错了,浪费了大半天的时间,本人也是菜鸟一枚.开始吧.(MVC的) @using Rattan.Co ...

  7. JavaScript 事件管理

    在设计JavaScript xxsdk的时候考虑到能让调用者参与到工作流程中来,开始用了回调函数.如下: this.foo = function(args,callbackFn) { //do som ...

  8. linux基础-第十三单元 硬盘分区、格式化及文件系统的管理二

    第十三单元 硬盘分区.格式化及文件系统的管理二 文件系统的挂载与卸载 什么是挂载 mount命令的功能 mount命令的用法举例 umount命令的功能 umount命令的用法举例 利用/etc/fs ...

  9. Redis事件管理(一)

    Redis统一的时间管理器,同时管理文件事件和定时器, 这个管理器的定义: #if defined(__APPLE__) #define HAVE_TASKINFO 1 #endif /* Test ...

随机推荐

  1. a 中调用js的几种方法

    我们常用的在a标签中有点击事件:1. a href="javascript:js_method();" 这是我们平台上常用的方法,但是这种方法在传递this等参数的时候很容易出问题 ...

  2. mysql_DML_delete

    delete from 表名   删除表里的数据 可以配合where试用

  3. mac jdk设置

    mac系统一般默认会安装jdk 1.6,路径为/System/Library/Java/JavaVirtualMachines/1.6.0.jdk,此Jdk为系统默认jdk; 但某些框架/应用要求jd ...

  4. ASP三种常用传值方式:

    ASP 页面(两个aspx页面)传值方式:背景: 两个aspx 页面valuepage.aspx tbusername tbpwdobtainvalue.aspx tbusername tbpwd 1 ...

  5. 使用Jeditable插件时遇到的问题

    Jeditable在渲染页面已有DIV=>form的时候 首先使用 $("div").html(); 去获取原DIV中的内容. 这样导致一个问题, 如果原div中带有html ...

  6. web服务器内置对象,或者说是ServletAPI的实例

    final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null;   // ...

  7. MongoDB - MongoDB CRUD Operations, Query Documents, Project Fields to Return from Query

    By default, queries in MongoDB return all fields in matching documents. To limit the amount of data ...

  8. SQLite&&SharedPreferences&&IO读写Sdcard学习笔记

    SQLite 轻量级的.嵌入式的.关系型数据库 Android.IOS等广泛使用的的数据库系统 SQLite数据库之中可以方便的使用SQL语句,实现数据的增加.修改.删除.查询等操作 SQLiteOp ...

  9. Swift 概述及Swift运算符和表达式

    Swift  是用于设计 iOS 及 Mac OS X 应用的一门新 语言. Swift 特点 •   Swift  保留了 C  与 Objective-C 的优点,并摒弃 其为了兼容 C  语言所 ...

  10. jqure获取单选按钮的值(比如性别)

    使用jquery获取radio的值,最重要的是掌握jquery选择器的使用,在一个表单中我们通常是要获取被选中的那个radio项的值,所以要加checked来筛选,比如有以下的一些radio项: 1. ...