Zend/EventManager,开发人员可以使用它来实现事件-监听器的机制, 从本篇开始将对其进行详细介绍。
先理解何谓事件/监听器机制,以及简易实现:
事件是一个点的概念,比如一个读操作完成前和完成后的两个状态就是两个事件;
监听器是一个执行过程,可以是一个匿名函数,也可以是一个对象的某个方法;
下面将举例解释事件和监听器的抽象演变;
思考如下功能:读取一个用户的用户名,先从内存(比如memache)读取,如果内存中没有,则到数据库(比如mysql)中读取,并在读出后将数据写入到内存。
1. 常规的实现
传统的代码如下,也可到这里查看代码eventManager_demo1.php
<?php /** * 模拟数据库 */ class DB { // 模拟存储在数据库的数据 private static $users = array( '1' => 'username1', '2' => 'username2', '3' => 'usermame3', ); static function getUserName($userId) { // 模拟数据库查询 return self::$users[$userId]; } } /** * 模拟缓存 */ class Cache { private static $users = array(); static function getUserName($userId) { return isset(self::$users[$userId]) ? self::$users[$userId] : ''; } static function setUserName($userId, $userName) { self::$users[$userId] = $userName; } } class User { private $userId; public function __construct($userId) { $this->userId = $userId; } public function getName() { // step1 先从内存读取用户名,如果存在就直接返回用户名 $userName = Cache::getUserName($this->userId); if ($userName) { return $userName; } // step2 从数据库读取用户名 $userName = DB::getUserName($this->userId); // step3 将用户名写入内存 Cache::setUserName($this->userId, $userName); // step4 return $userName; } } $user = new User('1'); var_dump($user->getName()); var_dump($user->getName()); /* 运行结果: read from Cache: read from DB: username1 write to Cache: username1 string(9) "username1" read from Cache: username1 string(9) "username1" */ ?>
以上代码在User::getName($userId)中,实现了功能主体逻辑;
2. 引入事件/监听器机制
现在我想使我的系统更灵活一些,比如外面在调用User::getName($userId)时,同样可以获取到期望的结果,但是在User::getName($userId)内部,不再去具体的去写和操作Cache有关的代码,这时候我们引入事件和监听器。
我们为step1定义一个名为’readCacheEvent’的事件,和名为’readCacheListener’的监听器;
为step3定义一个名为’writeCacheEvent’的事件,和名为’writeCacheListener’的监听器;
我要的效果是在User::getName($userId)内部去触发定义的事件后,对应的监听器就会执行。
我们引入一个简易版的EventManager来协助完成这个效果,
class EventManager { /** * 按对应关系存储事件和监听器 * @var array */ private static $events = array(); /** * 绑定事件 * 按对应关系存储事件和监听器 * @param string $eventName * @param callable $listener */ public static function attach($eventName, $listener) { self::$events[$eventName][] = $listener; } /** * 触发事件 * 根据对应的事件名称去执行监听器 * @param string $eventName * @param array $argv * @return <mixed> */ public static function trigger($eventName, $argv = array()) { $listeners = self::$events[$eventName]; $response = array(); foreach ($listeners as $listener) { $response[] = call_user_func($listener, $argv); } return $response; } }
这样我们有了负责绑定事件,触发事件的EventManager,我们再为事件的监听器创建一个对象来集中实现事件监听器的逻辑:
class CacheListener { static function readCacheListener($argv) { return Cache::getUserName($argv['userId']); } static function writeCacheListener($argv) { Cache::setUserName($argv['userId'], $argv['userName']); } }
效果马上就要出来了,现在需要把原来的User类中step1和step3的逻辑代码替换成事件触发的代码,替换后如下:
class User { private $userId; public function __construct($userId) { $this->userId = $userId; } public function getName() { // step1 触发事件 $results = EventManager::trigger('readCacheEvent', array('userId' => $this->userId)); $userName = $results[count($results) - 1]; if ($userName) { return $userName; } // step2 从数据库读取用户名 $userName = DB::getUserName($this->userId); // step3 触发事件 EventManager::trigger('writeCacheEvent', array('userId' => $this->userId, 'userName' => $userName)); // step4 return $userName; } }
ok, 最后一步,我们在程序运行开始时将监听器注册到相应的事件上,如下,完整代码请到这里查看eventManager_demo2.php
// 对事件注册监听器 EventManager::attach('readCacheEvent', 'CacheListener::readCacheListener'); EventManager::attach('writeCacheEvent', 'CacheListener::writeCacheListener'); $user = new User('1'); var_dump($user->getName()); var_dump($user->getName()); /* 运行结果: read from Cache: read from DB: username1 write to Cache: username1 string(9) "username1" read from Cache: username1 string(9) "username1" */
3. 总结:一个事件/监听器工具要考虑的几个方面:
a. 事件和监听器的对应关系, 如何注册监听器,触发事件
在EventManager内部我们使用一个二维数组$events来保存事件和监听器的对应关系
b. 在触发事件时,如何将当前上下文和参数传递给对应的监听器
在User内部触发事件时,把上下文参数传递给监听器
c. 在执行一个事件上的多个监听器时,对每个监听器的返回结果的处理和保存
在EventManager内部我们使用数组$response来保存每个监听器的返回结果,最后把$response返回个触发这个事件的函数比如User::getName(),由它决定如何处理$response.