V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lml12377
V2EX  ›  程序员

Symfony 中 event 的 Listener 和 Subscriber 的区别是什么?

  •  
  •   lml12377 · 2016-08-25 11:02:39 +08:00 · 3105 次点击
    这是一个创建于 2773 天前的主题,其中的信息可能已经有所发展或是发生改变。

    文档地址: http://symfony.com/doc/current/event_dispatcher.html

    讲两者区别的时候:

    Listeners or Subscribers

    Listeners and subscribers can be used in the same application indistinctly. The decision to use either of them is usually a matter of personal taste. However, there are some minor advantages for each of them:

    Subscribers are easier to reuse because the knowledge of the events is kept in the class rather than in the service definition. This is the reason why Symfony uses subscribers internally; Listeners are more flexible because bundles can enable or disable each of them conditionally depending on some configuration value.

    看 Symfony 的源码:

    HttpKernel -> handleRaw 中,当往 requestStack 中 push 完一个 Request 之后,会通过 EventDispatcherInterface::dispatch('kernel.request', new GetResponseEvent()),来触发所有监听 kernel.request 的 Listener 。

    这里有一些疑问,首先 GetResponseEvent 事件对象是事件源( HttpKernel )需要触发 kernel.request 这个事件的时候自己创建的,那监听 kernel.request 的 Listeners 是什么时候注册进去的?那 EventDispatcher 扮演了什么角色?另外发现 HttpKernel 中的 eventListener 是实现的 EventSubscriberInterface 接口,这个 Subscriber 不是订阅者的意思吗?那 Listener 实现 Subscriber ,是说明这两者是同一个概念?

    后来搜了设计模式,发现还有观察者模式

    事件源 /事件 /事件监听 /事件分发,发布 /订阅,观察者,这些概念之间是个什么关系?发现网上各种解释都有,都是一家之言,有的甚至还产生混淆。。。

    求解~~~

    6 条回复    2016-08-29 22:25:46 +08:00
    thenbsp
        2
    thenbsp  
       2016-08-25 16:12:44 +08:00   ❤️ 1
    我不太会组织这些技术含义,只说说我自己的理解,希望能帮到你!

    https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/HttpKernel.php#L128

    ```
    $event = new GetResponseEvent($this, $request, $type);
    $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
    ```

    EventDispatcher ,即调度器, dispatch 为动词,是指去调度(触发)某个事件,这个类似于 jQuery 的 trigger 和 angular 中的 $broadcast($emit)。

    在这里 KernelEvents::REQUEST 为事件名称,$event 是事件实体对象(当前事件的相关参数,也可以使用 Symfony\Component\EventDispatcher\GenericEvent 方法),说直白一点,就是当发生 KernelEvents::REQUEST 的时候,去做一个件事情,并把 $event 当作参数传递过去,但是你会发现只是定义了事件,并没有说去做的事情是什么!

    EventDispatcher 中还有一个方法, EventDispatcher::addListener($eventName, $listener, $priority = 0):

    https://github.com/symfony/symfony/blob/master/src/Symfony/Component/EventDispatcher/EventDispatcher.php#L102

    这个 addListener (监听器)就是用来指定前面所说的:告诉事件调度器,在某个事件发生时去做什么,第二个参数就是事件处理,举个例子:

    ```
    $this->dispatcher->addListener(KernelEvents::REQUEST, function(GetResponseEvent $event) {
    $request = $event->getRequest();
    var_dump($request->getClientIp());
    exit;
    });
    ```

    我监听了 KernelEvents::REQUEST 事件,来获取当前请求对象中的 IP 地址,可以看到,在事件处理中的 $event 就是事件发生时,传传递过来的 GetResponseEvent 事件实体!

    subscriber 与 listener 相同的,在 EventDispatcher::addSubscriber 内部去掉用了 addListener 方法,看源码:

    https://github.com/symfony/symfony/blob/master/src/Symfony/Component/EventDispatcher/EventDispatcher.php#L102

    subscriber 与 listener 的不同点在于,前者在定义阶段就指定了由谁来负责处理事件,而后者是在事件中去决定需要去捕获哪些事件(也就是 EventSubscriberInterface::getSubscribedEvents 方法),后者更灵活, Symfony 内部代码都是使用的 subscriber !
    thenbsp
        3
    thenbsp  
       2016-08-25 16:15:49 +08:00   ❤️ 1
    @thenbsp 最后一句写反了,应该是

    listener 与 subscriber 的不同点在于,前者在定义阶段就指定了由谁来负责处理事件,而后者是在事件中去决定需要去捕获哪些事件(也就是 EventSubscriberInterface::getSubscribedEvents 方法),后者更灵活, Symfony 内部代码都是使用的 subscriber !
    hantsy
        4
    hantsy  
       2016-08-26 09:12:58 +08:00   ❤️ 1
    好久没看到一个有营养的话题了。
    lml12377
        5
    lml12377  
    OP
       2016-08-27 10:14:18 +08:00
    @thenbsp 谢谢耐心的讲解,其实 listener 的原理是明白的,主要是 subscriber ,“而后者是在事件中去决定需要去捕获哪些事件”,这句中的前一个“事件”是不是指的 Subscriber ?指的是在 Subscriber 中获取需要监听的事件吗?

    是这样:首先往 Manager 中注册监听 name 事件的 N 个 Listener callable ,当事件源需要触发事件的时候,就调用 dispatch('name', new SomeEvent()) 来让 Manager 来挨个调用跟 name 关联的 Listener ,并把 SomeEvent 传递给这些 Listener , Listener 处理完会把它处理过的 SomeEvent 返回回来,事件源拿到处理过的 SomeEvent 继续处理。

    Symfony 中的 Listener 其实是实现了 EventSubscriberInterface 的,它会强制实现 getSubscribedEvents(),这个方法返回的就是当前 Listener 自己的每个 callable function 可以处理的 Event (意思是这个 Listener 其实是可以处理多个 name Event 的?),这里还有一个问题:既然 Subscriber 自己是知道自己可以处理哪些事件,并且知道调用自己的什么方法去处理,那是不是 Subscriber 是不需要提前注册的?如果这样的话,岂不是每次触发事件 Manager 需要把所有的 Subscriber 遍历一遍调用 getSubscribedEvents() 才能找到对应的 callable ?
    thenbsp
        6
    thenbsp  
       2016-08-29 22:25:46 +08:00   ❤️ 1
    @lml12377

    1 ,可以处理多个事件,即使是同一个 name ,也可以多次触发。
    2 ,当然需要注册,在注册 Subscriber 的时候,内部才会调用 addListener ,否则只是一个 callable 对象,没有和 eventName 绑定关系。

    https://github.com/symfony/symfony/blob/master/src/Symfony/Component/EventDispatcher/EventDispatcher.php#L30

    最终这些 event 是以 eventName 为 key 存储在 EventDispatcher::listeners 属性上的二维数组,寻找指定 eventName 不需要遍历,但触发时需要遍历!

    如果你仔细看过源码,你会发现第一个 callable 是不允许有返回值的(因为同一个事件可能会有多个 callable 被触发),因此我还专门自己写了个类似功能的事件管理。

    https://github.com/thenbsp/wechat/blob/master/src/Event/EventListener.php

    另外,这个组件相对不那么复杂,你可以使用 Composer 安装一下一步一步打印看看!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4783 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:37 · PVG 13:37 · LAX 22:37 · JFK 01:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.