如何通过配置而不是模块引导来附加事件监听器?

时间:2018-03-08 20:17:48

标签: php events zend-framework3

在ZF3中,您通常会在模块的Module.php中附加KeyFactory kf = KeyFactory.getInstance("ECDSA"); 的事件监听器,如下所示:

MvcEvent

现在有两种典型的情况,你的Module.php会变大:

  1. 您的模块必须处理多个(甚至全部)<?php namespace MyModule; class Module { public function onBootstrap(MvcEvent $event) { $eventManager = $event->getApplication()->getEventManager(); $eventManager->attach(MvcEvent::EVENT_DISPATCH, function(MvcEvent $event) { // Do someting... }); } } ,甚至可能以不同的方式对待它们。
  2. 您的模块必须对单个MvcEvent执行多项操作。
  3. 我希望能够做的是在module.config.php中指定一个类名以及一个或多个MvcEvent名称,以使我的Module.php保持干净整洁。 / p>

    有没有办法在Zend Framework 3中执行此操作?

2 个答案:

答案 0 :(得分:2)

@Nukeface有一个很好的例子,但它没有直接回答我的具体问题。

回答我自己的问题:

使用侦听器可以实现这一点。可以在配置文件中配置侦听器,但不能直接从配置中将其映射到事件。

可以检查配置中的特定设置,并确定要映射到哪些类的事件。甚至可以用这种方式映射MvcEvent

以下是如何进行设置:

1。听众

我们希望通过一个简单的类来听多个MvcEvent。注意它扩展的类。

namespace Demo\Listener;

class MyListener extends EventClassMapListener
{
    public function handleEvent(MvcEvent $event)
    {
        // Do something
        \Zend\Debug\Debug::dump($event->getName());
    }
}

2。抽象侦听器类

上面的类需要更多的主体,但可以由抽象监听器类提供:

namespace Demo\Listener;

abstract class EventClassMapListener implements ListenerAggregateInterface
{
    private $configuration;

    public function __construct(array $configuration)
    {
        $this->configuration = $configuration;
    }

    public function attach(EventManagerInterface $events, $priority = 1)
    {
        $sharedManager = $events->getSharedManager();
        foreach ($this->configuration as $identifier => $settings) {
            foreach ($settings as $event => $configPriority) {
                $sharedManager->attach($identifier, $event, [$this, 'handleEvent'], $configPriority ?: $priority);
            }
        }
    }

    public function detach(EventManagerInterface $events)
    {
        // Do the opposite of attach
    }

    abstract public function handleEvent(MvcEvent $event);
}

3。工厂

现在我们需要一个工厂,我们可以重用所有需要监听多个事件的类:

namespace Demo\Factory\Listener;

class EventClassmapListenerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $globalConfiguration = $container->get('config');
        $configuration       = [];

        if (array_key_exists('event_classmap', $globalConfiguration)
            && array_key_exists($requestedName, $globalConfiguration['event_classmap'])
        ) {
            $configuration = $globalConfiguration['event_classmap'][$requestedName];
        }

        return new $requestedName($configuration);
    }
}

4。构造

在你的module.config.php中:

'service_manager' => [
    'factories' => [
        Listener\MyListener::class => Factory\Listener\EventClassmapListenerFactory::class,
    ],
],

'listeners' => [
    Listener\MyListener::class,
],

'event_classmap' => [
    // Name of the class that needs to listen to events
    Listener\MyListener::class => [
        // Identifier
        \Zend\Mvc\Application::class => [
            // List of event names and priorities
            MvcEvent::EVENT_BOOTSTRAP => 1,
        ],
        // Another identifier
        MyEventEmitterClass::class => [
            MyEventEmitterClass::EVENT_ONE,
            MyEventEmitterClass::EVENT_TWO,
            MyEventEmitterClass::EVENT_THREE,
        ],
    ],
],

结论:

虽然它可能没有真正改进,但我真的很喜欢这个想法。现在可以很容易地添加另一个侦听器并使其侦听来自一个或多个发射器的事件列表。

经过一番研究后我的意见

听众本身应说出要听的内容,严格要求。将该信息放在配置文件中可能会在不需要时导致更复杂的情况。

答案 1 :(得分:1)

您需要为Listener类提供一些东西:

  1. 活动
  2. 监听
  3. 处理程序
  4. 工厂
  5. 配置
  6. 现在,2&amp; 3通常与您通常具有用于特定目的的Listener类属于同一类。比如“听火箭发射并将火箭转向火星”。

    因此,您需要“创建”这些“事件”来倾听某个地方。比如DemoEvents类!

    namespace Demo\Event;
    
    use Zend\EventManager\Event;
    
    class DemoEvent extends Event
    {
        const THE_STRING_TO_LISTEN_FOR = 'rocket.ready.for.launch';
        const ANOTHER_STRING_TO_LISTEN_FOR = 'rocket.steer.to.mars';
    }
    

    现在我们有“事件”,我们需要“倾听”他们。为此我们需要一个监听器。因为我将此限制为1个示例,处理程序(当我们“正在侦听”的“事件”被“听到”时要执行的函数(-ality)将在同一个类中。

    namespace Demo\Listener;
    
    use Demo\Event\DemoEvent;
    use Zend\EventManager\Event;
    use Zend\EventManager\EventManagerInterface;
    use Zend\EventManager\ListenerAggregateInterface;
    
    class DemoListener implements ListenerAggregateInterface
    {
        /**
         * @var array
         */
        protected $listeners = [];
    
        /**
         * @param EventManagerInterface $events
         */
        public function detach(EventManagerInterface $events)
        {
            foreach ($this->listeners as $index => $listener) {
                if ($events->detach($listener)) {
                    unset($this->listeners[$index]);
                }
            }
        }
    
        /**
         * @param EventManagerInterface $events
         */
        public function attach(EventManagerInterface $events, $priority = 1)
        {
            $sharedManager = $events->getSharedManager();
    
            $sharedManager->attach(Demo::class, DemoEvent::THE_STRING_TO_LISTEN_FOR, [$this, 'doSomethingOnTrigger'], -10000);
        }
    
        /**
         * Apart from triggering specific Listener function and de-registering itself, it does nothing else. Add your own functionality
         *
         * @param Event $event
         */
        public function doSomethingOnTrigger(Event $event)
        {
           // Gets passed along parameters from the ->trigger() function elsewhere
            $params = $event->getParams(); 
    
            $specificClass = $params[SpecificClass::class];
    
            // Do something useful here
            $specificClass->launchRocketIntoOrbit();
    
            // Detach self to prevent running again
            $specificClass->getEventManager()->getSharedManager()->clearListeners(get_class($specificClass), $event->getName());
    
            // NOTE: USE THIS TRIGGER METHODOLOGY ELSEWHERE USING THE STRING FROM THE ATTACH() FUNCTION TO TRIGGER THIS FUNCTION
            // Trigger events specific for the Entity/class (this "daisy-chains" events, allowing for follow-up functionality)
            $specificClass->getEventManager()->trigger(
                DemoEvent::ANOTHER_STRING_TO_LISTEN_FOR,
                $specificClass ,
                [get_class($specificClass) => $specificClass ] // Params getting passed along
            );
        }
    }
    

    优异。我们现在有一个事件,一个监听器和一个处理程序。我们只需要一个工厂就可以在需要时创建这个类。

    namespace Demo\Factory;
    
    use Demo\Listener;
    use Interop\Container\ContainerInterface;
    use Zend\ServiceManager\Factory\FactoryInterface;
    
    class DemoListenerFactory implements FactoryInterface
    {
        /**
         * @param ContainerInterface $container
         * @param string $requestedName
         * @param array|null $options
         * @return object|DemoListener
         * @throws \Psr\Container\ContainerExceptionInterface
         * @throws \Psr\Container\NotFoundExceptionInterface
         */
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            // If you're implementation of the Listener has any requirements, load them here and add a constructor in the DemoListener class
    
            return new DemoListener();
        }
    }
    

    最后,我们需要一些配置。显然我们需要注册Listener + Factory组合。我们先做。

    namespace Demo;
    
    use Demo\Listener\DemoListener;
    use Demo\Listener\DemoListenerFactory;
    
    'service_manager' => [
        'factories' => [
            DemoListener::class => DemoListenerFactory::class,
        ],
    ],
    

    现在有一点知道配置,以确保监听器被注册为监听器:

    'listeners' => [
        DemoListener::class
    ],
    
    是的,就是这样。

    确保在配置的第一级添加这两个配置位,它们是兄弟姐妹。