symfony中的事件委派是一个比较经典的设计模式,这个机制对代码的解耦有很大的帮助。理解事件委派的机制有助于对源代码的理解,比如路由的解析就是基于事件委派来实现的。

事件触发

这里是事件触发的代码,从这个代码作为切入点进行代码调用逻辑的梳理

1
2
3
4
5
6
7
8
# Symfony\Component\HttpKernel\HttpKernel

private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
$event = new GetResponseEvent($this, $request, $type);
// 触发事件
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
}

委派器以及Listener

以上代码的$this->dispatcher是需要从容器里查找 在创建 HttpKernel 这个类的时候初始化了哪些参数。
从容器代码中查找分析,委派器类的实例化以及对事件增加 Listebner 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# var/cache/prod/appProdProjectContainer.php 
protected function getHttpKernelService()
{
return $this->services['http_kernel'] = new \Symfony\Component\HttpKernel\HttpKernel(
// 这里是dispatcher参数 还是要在容器里取
${($_ = isset($this->services['event_dispatcher']) ? $this->services['event_dispatcher'] : $this->get('event_dispatcher')) && false ?: '_'},
// ......
);
}

protected function getEventDispatcherService()
{
// 实例化 dispatcher
$this->services['event_dispatcher'] = $instance = new \Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher($this);
// 添加事件
// 添加的是
$instance->addListener('kernel.response', function (\Symfony\Component\HttpKernel\Event\GetResponseEvent $event) {
return ${(
$_ = isset($this->services['session_listener']) ?
$this->services['session_listener'] :
$this->get('session_listener')
) && false ?: '_'}->onKernelRequest($event);
}, 128);
$instance->addListener('kernel.request', /* ...... */);
$instance->addListener('kernel.finish_request', /* ...... */);
$instance->addListener('console.terminate', /* ...... */);
// ......
}

委派器的工作方式

从上面的代码可以看出,委派器的Listener触发方式 $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);。基于此对委派器工作的逻辑代码进行跟中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
public function getListeners($eventName = null)
{
if (null === $eventName) {
foreach ($this->listenerIds as $serviceEventName => $args) {
$this->lazyLoad($serviceEventName);
}
} else {
$this->lazyLoad($eventName);
}

return parent::getListeners($eventName);
}
# Symfony\Component\EventDispatcher\EventDispatcher
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
// 针对委派器查询获取 对应的 Listener
if ($listeners = $this->getListeners($eventName)) {
$this->doDispatch($listeners, $eventName, $event);
}

return $event;
}
public function getListeners($eventName = null)
{
// 对Listeners的处理 排序 过滤等操作
if (null !== $eventName) {
if (!isset($this->listeners[$eventName])) {
return array();
}

if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}

return $this->sorted[$eventName];
}

foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}

return array_filter($this->sorted);
}
protected function doDispatch($listeners, $eventName, Event $event)
{
// 对委派器绑定的Listeners循环处理
foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
break;
}
// Listener绑定的是匿名函数,这里调用匿名函数,做到延迟加载
call_user_func($listener, $event, $eventName, $this);
}
}