大家好,这篇文章将通过我在实际开发工作中的例子,来介绍Symfony的EventDispatcher组件的使用及实现原理。
这个组件在实际开发过程中非常的有用,它能够使代码的业务逻辑变的非常清晰,增加代码的复用性,代码的耦合性也大大降低。
简介
具体的介绍大家可以查看官方的文档,下面是文档地址。
文档地址
组成
一个 dispatcher 对象,保存了事件名称和其对应监听器
一个 event,有一个全局唯一的事件名称。包含一些在订阅器里需要访问的对象。
使用示例
1. 初始化,添加相应监听事件
# 初始时,添加监听器 $dispatcher = new EventDispatcher(); $disptacher->addSubscriber(new BIReportSubscriber()); // BI上报功能 $disptacher->addSubscriber(new MediaPlayerSubscriber()); // 维护播放器信息统一
Symfony\Component\EventDispatcher\EventDispatcher
2. 监听的事件
class BIReportSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents () { // 监听的不同事件,当事件触发时,会调用 onResponse 方法 return [ MusicResponseEvent::NAME => 'onResponse', ChildrenResponseEvent::NAME => 'onResponse', FmResponseEvent::NAME => 'onResponse', NewsResponseEvent::NAME => 'onResponse', ]; } public function onResponse(AResponseEvent $event) { /* * 一些具体的业务逻辑 * 进行 BI 上报 */ }
class MediaPlayerSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents () { return [ MusicResponseEvent::NAME => 'onResponse', FmResponseEvent::NAME => 'onResponse', ChildrenResponseEvent::NAME => 'onResponse', NewsResponseEvent::NAME => 'onResponse', ]; } public function onResponse(AResponseEvent $event) { /* * 一些具体的业务逻辑 * 维护播放器信息统一 */ }
实现 getSubscribedEvents 方法,完成事件的绑定。当事件触发时,dispatcher 会调用绑定的方法,并将抛出的事件当做参数传入。
事件绑定的方法 onResponse 可以是任何名字。
在 onResponse 方法中,通过 $event 获取要操作的对象。
3. 事件代码
class FmResponseEvent extends Event { const NAME = 'fm.response'; // 事件名,事件的唯一标识 protected $request; // 在监听器里要操作的对象 protected $response; // 在监听器里要操作的对象 public function __construct (Request $request, Response $response) { $this->request = $request; $this->response = $response; } /** * @return Request */ public function getRequest() { return $this->request; } /** * @return Response */ public function getResponse() { return $this->response; } }
- 继承
Symfony\Component\EventDispatcher\Event
- 在订阅器的业务逻辑上,需要使用
$request 和 $response
对象,所以本事件包含这两个类的对象。
4. 触发事件
$event = new FmResponseEvent($request, $response); $dispatcher->dispatch($event::NAME, $event);
dispathcer
会按照优先级,依次执行订阅器中事件绑定的方法
原码解读
1 简化的 EventDispatcher 源码
class EventDispatcher implements EventDispatcherInterface { private $listeners = array(); private $sorted = array(); /** * 触发事件 */ public function dispatch($eventName, Event $event) { if ($listeners = $this->getListeners($eventName)) { $this->doDispatch($listeners, $eventName, $event); } return $event; } /** * 根据事件名,搜索监听器 */ public function getListeners($eventName) { if (empty($this->listeners[$eventName])) { return array(); } if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } return $this->sorted[$eventName]; } /** * 换优先级将监听器排序 * @param string $eventName */ private function sortListeners($eventName) { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = array(); foreach ($this->listeners[$eventName] as $priority => $listeners) { foreach ($listeners as $k => $listener) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { $listener[0] = $listener[0](); $this->listeners[$eventName][$priority][$k] = $listener; } $this->sorted[$eventName][] = $listener; } } } protected function doDispatch($listeners, $eventName, Event $event) { foreach ($listeners as $listener) { if ($event->isPropagationStopped()) { break; } \call_user_func($listener, $event, $eventName, $this); } /** * 添加订阅器 */ public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { $this->addListener($eventName, array($subscriber, $params)); } elseif (is_string($params[0])) { $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); } else { foreach ($params as $listener) { $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); } } } } public function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName]); } }