ZendFramework3事件驱动架构核心模块zend-eventmanager
来源:互联网 发布:ubuntu 14.04安装qq 编辑:程序博客网 时间:2024/06/03 22:46
前几天看到一个知乎的网友提问如何在业务中避免出现复杂的if…else…逻辑,其中一个答友回答需要去看大型框架的实现.由于个人认为ZF3(ZendFramework3的简写)的事件驱动模块实现的很优雅,有很多值得借鉴的地方,并且恰好解决了这位网友的疑问.
0x00. 什么是事件驱动
一句话解释:先绑定,后触发的逻辑实现.
举个栗子:
小明
是个厨师.如果他工作在一家炒菜馆.顾客
进店,点了叫A
,B
,C
的三道菜,小明得到店小二
后立即从自己的大脑中回忆这三种菜的做法,并排序.接着通过一系列娴熟的操作,将三道菜按照预先的排序呈现给顾客.此时来了第二个顾客,点了
D
菜和S
汤以及主食N
,这时候小明又被触发D,S,N背后的动作,最终完成任务.
在上面这个例子中:
顾客
:作为事件的绑定者,可以决定口味清淡与否,或者不加某种作料.
A
,B
,C
:绑定后的事件.
店小二
:负责通知,其实就是持有事件并且触发的管理者.
小明
:负责出发后的逻辑实现.
D
,S
,N
:下一个顾客绑定的事件.
这就是事件驱动,总结来说就是将一系列基础操作逻辑封装好,绑定给一个事件,当这个事件被触发时,回调之前封装好的逻辑.
1. 小明的操作就是事件的具体内容.
2. 菜单上的菜名就是事件的名字.
3. 顾客来吃饭就是触发一系列事件的条件.
4. 菜品就是整个事件的返回值.
如果小明工作在一家快餐店,情况将完全不同.他先将所有的菜做好,然后直接卖个顾客.
这就成了缓存机制,可能资源质量有限,无法达到顾客最想要的程度,但是速度快.
但是饭店一般的做法就是现将永恒不变的资源缓存好,然后剩下的逻辑通过事件驱动的方法,达到定制化要求.
0x01. 实现原理
在ZF3中,通过zend-eventmanager子模块管理整个框架中的事件.将每个阶段分拆成若干事件.
例如,在程序初始化阶段(调用
Zend\Mvc\Application::init($applicationConfig)
),需要加载所有模块,这时候zend-modulemanager将这阶段常规触发的事件定义为下列4个:
loadModules
:主要负责出发下面两个事件.
loadModule
:实例化每个模块入口,并读取主要配置,依赖检查,模块初始化,向bootstrap阶段绑定事件等等.
mergeConfig
:合并所有模块的配置文件.
loadModules.post
:配置所有servicemanager(Controller和Router等模块的servicemanager).
然后在适当的时机由zend-eventmanager触发.
实现这种类似lazy service的原理是所有绑定在事件下的逻辑都是callable类型,其中包括带有__invoke()的对象
,闭包
,数组构成的callable
等.
0x02. 名词解释
如果你不了解ZF3的事件驱动,建议先移步zend-eventmanager手册看个大概.
你可能已经部分或者全部的阅读过zend-eventmanager的源码,相信如果你在某个阶段一定有疑问,在zend-eventmanager中,诸如Event,Listener,EventManager,SharedEventManager到底是什么意思,以下逐一先做解释:
Listener
:存放着主要业务逻辑的对象,在事件下绑定的多半就是Listener的方法(小明炒菜的各种操作).
Event
:容器,负责存放Listener中可能用到的对象,可能是整个框架的ServiceManager,也可能是一个变量(厨房,有工具,有原材料).
EventManager
:事件驱动的管理者,负责绑定事件,存放事件,触发事件等(店小二,负责通知小明开始按照要求做菜).
SharedEventManager
:有时候需要为别的阶段绑定事件.例如在ZF3加载模块的时候要向bootstrap阶段添加事件怎么办?因为bootstrap不仅和加载模块是两个完全独立的事件,而且在加载模块的时候bootstrap事件还不为EventManager所知.这时候就要将事件暂时存放在SharedEventManager中,在稍后EventManager读取bootstrap事件的时候会去查看SharedEventManager中有没有此阶段的逻辑,如果有的话一并触发(如果顾客想给旁边桌的顾客点一道菜,负责将这件事记录下来的本子就是SharedEventManager).
0x03. zend-eventmanager
这一节将从实例化zend-eventmanager模块开始,介绍这一模块的使用方法.
安装:
composer require zendframework\zend-eventmanager
然后在自己的项目中:
require(__DIR__ . '/vendor/autoload.php');use Zend\EventManager\EventManager;$events = new EventManager();
这就得到了zend-eventmanager模块的入口实例.这时就使用其提供的接口,添加,触发事件.
例1:绑定一个输出hello world的匿名函数给一个叫simpleEvent的事件.
//绑定$events->attach('simpleEvent', function () { echo 'hello world'; });//触发$events->trigger('simpleEvent');//输出://hello world
例2:绑定两个匿名函数,一个输出hello,另一个输出XiaoMing,要求两个按照固定顺序执行,即hello Xiaoming.
这时候就涉及到attach()方法的第三个参数,优先级.
//绑定$events->attach('simpleEvent', function () { echo 'hello '; }, 100);//再次绑定$events->attach('simpleEvent', function () { echo 'XiaoMing'; }, 99);//按照优先顺序触发$events->trigger('simpleEvent');//输出://hello XiaoMing
例3:如果我们要向绑定的方法中传入参数怎么办?例如,我们用参数来代替hello后的输出内容.
//绑定并获取默认的Event容器$events->attach('simpleEvent', function ($event) { echo 'hello ' . $event->getParams()['name']; }, 100);//触发,并传入参数$events->trigger('simpleEvent', null, ['name' => 'Lily']);//输出// hello Lily
这里例子中,出现了一个新东西Event,Event是一个容器,用于封装所有Listener(其实就是绑定的那个匿名函数)需要的数据.
具体来说,Event是zend-eventmanager提供的用于封装Listener所需上下文,变量等的一个容器,可以自己定义(稍后介绍),也可以像上文三个例子中的,让zend-eventmanager自动生成一个默认的Event.
默认的Event包括target和params以及name三大部分,target一般用于存放上下文,可以使用getTarget(),setTarget()两个接口来操作,剩下两部分接口类似,都是标准的get,set.params用于存放trigger中的第三个数组参数,以上例为例,是指['name' => 'Lily']
,而name则是当前事件的名字,也就是上例中的simpleEvent.
zend-eventmanager在触发绑定Listener的时候会讲Event作为参数传入.
这就是将参数传入Listener中的方法.
trigger中的第二个参数就是target.
例4:例3的另外一种触发方式.自己构建Event,然后触发.
//绑定$events->attach('simpleEvent', function ($event) { //调用自己实现的接口 echo 'hello ' . $event->getUser();});use Zend\EventManager\Event;//自己构建Event,这是一种偷懒的方法,不推荐,读者请引用//Zend\EventManager\EventInterface认真构建class MyEvent extends Event{ protected $user; public function setUser(string $user) { $this->user = $user; } public function getUser() { return $this->user; } } $myEvent = new MyEvent;//设置好事件的名字$myEvent->setName('simpleEvent');//自己实现的接口$myEvent->setUser('Lucy');//触发$events->triggerEvent($myEvent);//输出://hello Lucy
这里自己添加了接口getUser()
和setUser()
,用于在Listener中更便捷的访问到变量.
同时还使用了另外一种触发方式triggerEvent()
,用于直接触发Event对象.
例5:如果需要从Listener中获取数据怎么办?也就是说Listener的返回值如何接收?
//绑定$events->attach('simpleEvent', function ($event) { //调用自己实现的接口 return 'hello ' . $event->getUser();});use Zend\EventManager\Event;//自己构建Event,这是一种偷懒的方法,不推荐,读者请引用//Zend\EventManager\EventInterface认真构建class MyEvent extends Event{ protected $user; public function setUser(string $user) { $this->user = $user; } public function getUser() { return $this->user; } } $myEvent = new MyEvent;//设置好事件的名字$myEvent->setName('simpleEvent');//自己实现的接口$myEvent->setUser('Lucy');//触发$responses = $events->triggerEvent($myEvent);//获得返回值echo $responses->pop();//输出://hello Lucy
由此可见zend-eventmanager将所有Listener的返回值全部封装在$responses
中,使用current()
,next()
,pop()
,isEmpty()
等接口顺利取出返回值(详见Zend\EventManager\ResponseCollection
).
例6:在绑定了多个事件的时候,如何条件的停止事件继续触发?
到目前为止,我们可以顺利的使用zend-eventmanager模块了,但是,一个事件一旦被触发,其Listener将被尽数执行,有没有方法在某种条件下停止执行呢?
zend-eventmanager还提供了两种触发方式triggerUntil()
和triggerEventUntil()
,用于条件停止事件继续执行.
//绑定$events->attach('simpleEvent', function () { return 'Lucy'; }, 100);//再次绑定$events->attach('simpleEvent', function () { return 'XiaoMing'; }, 99);$events->attach('simpleEvent', function () { return 'Lily'; }, 98);//当遇到返回值XiaoMing时停止执行$responses = $events->triggerUntil(function ($name) { if($name === 'XiaoMing') return TRUE; return FALSE;},'simpleEvent');while(! $reponses->isEmpty()) { echo $responses->pop();}//输出://XiaoMing//Lucy
由上例可见,最后一个绑定的Listener没有执行.
triggerUntil()
和triggerEventUntil()
两个借口和trigger()
以及triggerEvent()
分类似,只不过加了第一个参数,一个判断是否停止事件继续执行的匿名函数,传入这个匿名函数的参数是当前Listener的返回值.
另外,Event的stopPropagation()
接口被调用也会停止当前事件继续执行.这个比较简单,不做演示.
例7:假设有两个事件event1和event2,event1先执行,如何在event1执行时动态的给event2添加Listener?
use Zend\EventManager\EventManager;use Zend\EventManager\SharedEventManager;//实例化SharedEventManager$shared = new SharedEventManager;//实例化EventManager并注入SharedEventManager$events = new EventManager($shared);//绑定event1的Listener$events->attach('event1', function () { echo 'listener1 from event1'; }, 100);$events->attach('event1', function () { echo 'listener2 from event1'; }, 99);//利用SharedEventManager向event2绑定Listener,标识为SharedEvent$events->getSharedManager()->attach('sharedEvent', 'event2', function () { echo 'listener3 from event1 shared'; });//触发event1$events->trigger('event1');//输出://listener1 from event1//listener2 from event1
SharedEventManager刚好可以满足本例需求,SharedEventManager有两个指标来确定一个Listener何时触发,Identifiers
和事件名
,也就是上例中的’sharedEvent’和’event2’.EventManager取得SharedEventManager中的事件时,会对比EventManager当前的Identifiers和所触发的事件名,然后针对的触发Listener.
以下是出发event1为event2绑定的Listener,标识为SharedEvent.
//设置当前EventManager的标识,以取得SharedEventManager对应的事件.$events->setIdentifiers(['sharedEvent', 'otherIdentifiers']);$events->attach('event2', function () { echo 'listener4 from event2'; }, -100);$events->trigger('event2');//输出://listener3 from event1 shared//listener4 from event2
以上就是zend-eventmanager的主要用法.
尝试解答那位网友的问题,是不是可以通过事件驱动来解决问题?
业务中遇到较为冗长if…else…时,可以将其中的逻辑全部封装为多个独立的Listener,然后绑定到设计好的多个事件中,然后使用
triggerUntil()
和triggerEventUntil()
,传入用于判断的匿名函数.或者根据情况动态的绑定事件.
- ZendFramework3事件驱动架构核心模块zend-eventmanager
- 核心模块,事件驱动-03
- ZendFramework3数据库操作zend-db
- Zend/EventManager(Part2)
- zf2分析:Zend/EventManager(Part1)
- 【Unity3D】 EventManager事件管理器
- HBase架构核心模块
- tomcat架构:核心架构模块
- 核心模块之事件模块
- Zend Framework 2 中的EventManager的使用方法
- Unity3D C#事件管理:EventManager
- EventManager
- 事件驱动架构要点
- EDA事件驱动架构
- EDA事件驱动架构
- EDA事件驱动架构
- 事件驱动架构
- 读书笔记-事件驱动架构
- 第九章(集合)
- STL STACK
- MySql学习笔记(三)
- 奇数单增序列
- 用代码调出电脑所有的WiFi及密码
- ZendFramework3事件驱动架构核心模块zend-eventmanager
- Codeforces Round #439 C.The Intriguing Obsession(DP + 思维)
- 创建模型
- jQuery实现TextArea字数限制
- Apache MINA(网络应用程序框架)实例
- MySql安装遇到的问题
- 正则表达式:后向引用
- 自定义TopBarTest
- 焦点轮播图(5) 动画函数