Events Symfony2从服务中正确地挂接事件

Events Symfony2从服务中正确地挂接事件,events,symfony,service,Events,Symfony,Service,我有一个类,用于从各种相互连接的bundle生成导航。我有一个导航服务来完成这项工作 为了将此服务与其他导航位连接起来,我希望允许其他bundle定义它们自己的服务,这些服务随后侦听事件侦听器,并在适当的时间添加它们的导航项 问题是,如果不先手动调用服务来创建它,我就不知道如何让服务侦听事件 有什么想法吗 为了给出更具体的想法,我有这样的想法: // Set up as a service in the bundle. class Navigation { // ... pro

我有一个类,用于从各种相互连接的bundle生成导航。我有一个导航服务来完成这项工作

为了将此服务与其他导航位连接起来,我希望允许其他bundle定义它们自己的服务,这些服务随后侦听事件侦听器,并在适当的时间添加它们的导航项

问题是,如果不先手动调用服务来创建它,我就不知道如何让服务侦听事件

有什么想法吗


为了给出更具体的想法,我有这样的想法:

// Set up as a service in the bundle.
class Navigation {
    // ...
    protected $dispatcher; // event dispatcher passed in to service

    // ...
    public function generateNavigation() {
        $items = array();
        // add some items

        $event = new NavigationEvent($items); // custom event
        $this->eventDispatcher->dispatchEvent('navigation_event', $event);
    }
}

// Set up as a service in some secondary bundle.
class NavigationWorker {
    /**
     * @param $dispatcher Same instance as Navigation
     */
    public function __construct(EventDispatcher $dispatcher) {
        $dispatcher->addListener('navigation_event', array($this, 'doSomething'));
    }
}
有了这个设置,如果NavigationWorker在某个点被调用并被构造,它应该可以工作,但是我不能总是直接调用它们,所以它永远不会被构造,侦听器也永远不会被添加


我目前的做法是将所有NavigationWorkers传递到Navigation,并让它添加他们的侦听器,但这非常难看。

请参阅。生成
NavigationWorker
和事件侦听器,它不需要显式构造。

我正在更改此问题的答案,因为虽然这使我走上了正确的道路,但它不是完整的答案。那篇文章实际上只允许您连接到预定义的内核事件。但是我需要我自己的,所以我从那里开始工作

最后,我创建了自己的标记,一个用于处理这些任务的编译器传递。我还添加了自己的EventDispatcher扩展,尽管这不是非常必要(您可以使用普通的扩展)

下面是文件解决方案的外观

配置:

parameters:
    my_bundle.navigation.event.class: My\Bundle\DependencyInjection\NavigationEvent

    my_bundle.event_dispatcher.class: My\Bundle\DependencyInjection\EventDispatcher
    my_bundle.navigation.class: My\Bundle\DependencyInjection\NavigationGenerator
    my_bundle.navigation_listener1.class: My\Bundle\DependencyInjection\NavigationListener
    my_bundle.navigation_listener2.class: My\Bundle\DependencyInjection\NavigationListener

services:
    my_bundle.event_dispatcher:
        class: %my_bundle.event_dispatcher.class%
    my_bundle.navigation:
        class: %my_bundle.navigation.class%
        arguments:
            - @my_bundle.event_dispatcher
    my_bundle.navigation_listener1.class:
        class: %my_bundle.navigation_listener1.class%
        tags:
            - { name: my_bundle.event_listener, event: my_bundle.navigation.generate, method: onGenerateNavigation }
    my_bundle.navigation_listener2.class:
        class: %my_bundle.navigation_listener2.class%
        tags:
            - { name: my_bundle.event_listener, event: my_bundle.navigation.generate, method: onGenerateNavigation }
编译器类:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;

class EventListenerCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('my_bundle.event_dispatcher')) {
            return;
        }

        $definition = $container->getDefinition(
            'my_bundle.event_dispatcher'
        );

        $taggedServices = $container->findTaggedServiceIds(
            'my_bundle.event_listener'
        );

        foreach ($taggedServices as $id => $tagAttributes) {
            foreach ($tagAttributes as $attributes) {
                $definition->addMethodCall(
                    'addListener',
                    array($this->getEventString($attributes['event'], $container),     array(new Reference($id), $attributes['method']))
                );
            }
        }
    }

protected function getEventString($str, ContainerBuilder $container)
{
    preg_match('/(.*)\.([^.]*)$/', $str, $matches);
    $parameterName = $matches[1];
    $constName = strtoupper($matches[2]);

    $eventClass = $container->getParameter($parameterName . '.event.class');

    if (!$eventClass) {
        throw new Exception('Unable to find parameter: ' . $eventClass . '.event.class');
    }

    // Return the value of the constant.
    return constant($eventClass . '::' . $constName);
}
将这样的函数添加到编译器类中(类似于MyBundleBundle)

现在EventListener将为每个事件添加侦听器。您不仅可以完全按照预期实现其他所有功能(导航也会发送它所侦听的事件)。您可以从任何捆绑包中钩住新的事件侦听器,它们甚至不需要共享公共类/接口

这也适用于任何自定义事件,只要具有事件常量的对象在结尾处的参数“.event.class”中注册(因此my_bundle.navigation.generate查找参数my_bundle.navigation.event.class,使用该类和常量generate)

希望这能帮助其他想做类似事情的人

public function build(ContainerBuilder $container)
{
    parent::build($container);

    $container->addCompilerPass(new EventListenerCompilerPass());
}