Zend Framework 2在ListenerAggregateInterface中分离监听器

时间:2014-06-19 11:08:39

标签: zend-framework2

我实施了ListenerAggregateInterface来监听调度(MvcEvent::EVENT_DISPATCH)事件。

public function attach(EventManagerInterface $events) {
  $this->listeners[] = $events->attach(
    MvcEvent::EVENT_DISPATCH,
    array($this, 'shortCircuit'),
    101
  );

  $this->listeners[] = $events->attach(
    MvcEvent::EVENT_DISPATCH,
    array($this, 'listener1'),
    33
  );

  $this->listeners[] = $events->attach(
    MvcEvent::EVENT_DISPATCH,
    array($this, 'listener2'),
    33
  );
}

如果shortCiruit中的某些条件为true,则应跳过其余的侦听器。因此,我致电ListenerAggregateInterface::detach删除所有听众。

public function shortCircuit(MvcEvent $e) {
  if(condition) {
    $this->detach($e->getApplication()->getEventManager());
  }
}

我原本以为他们现在不再被执行了,但事实并非如此。

2 个答案:

答案 0 :(得分:6)

在调度侦听器时,为当前触发的事件分离侦听器不起作用。这是因为the listeners are collected and sorted before they are actually executed是为了按优先级对它们进行排序。

停止传播也不适用于您当前的方法,因为您只想禁用一组特定的侦听器。

但是有一个解决方案,即仅在需要时跳过监听器,使用小型注册表记录聚合侦听器要跳过的事件。我把它写在了我的头顶,所以它没有经过测试,如果你想使用它,请仔细测试它:

use SplObjectStorage;
use Zend\EventManager\EventInterface;

final class SkippedEventsRegistry {
    /** @var SplObjectStorage */
    private $skippedEvents;

    public function __construct() {
        $this->skippedEvents = new SplObjectStorage();
    }

    /**
     * @param callable $callback
     * @return callable
     */
    public function buildCallback(callable $callback)
    {
        return function ($event) use ($callback) {
            if (isset($this->skippedEvents[$event])) {
                return;
            }

            return $callback($event);
        };
    }

    public function skipListenersForEvent(EventInterface $event) {
        $this->skippedEvents[$event] = $event;
    }

    public function restoreListenersForEvent(EventInterface $event) {
        unset($this->skippedEvents[$event]);
    }
}

然后我们在聚合侦听器中使用此注册表:

use Zend\EventManager\EventInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\ListenerAggregateTrait;

class MyAggregateListener implements ListenerAggregateInterface {
    use ListenerAggregateTrait;
    private $skippedEvents;
    public function __construct() {
        $this->skippedEvents = new SkippedEventsRegistry();
    }

    public function attach(EventManagerInterface $events) {
        $this->listeners[] = $events->attach('SomeEvent',
            $this->skippedEvents->buildCallback(function ($event) {
            // ... do other things here ...
            if ($worldIsExploding) {
                    // start skipping the other listeners
                    $this->skippedEvents->skipListenersForEvent($event);
                }
            }), 9999);

        $this->listeners[] = $events->attach('SomeEvent',
            $this->skippedEvents->buildCallback(function ($event) {
                // ... do other things here ...
            }), 8888);

        // reset normal execution 
        // (sadly, happens only if propagation wasn't stopped)
        $this->listeners[] = $events->attach(
            'SomeEvent',
            [$this->skippedEvents, 'restoreListenersForEvent'],
            -9999999999
        );
    }
}

(很抱歉对齐混乱,但很难让溢出中的所有东西都适合:\)

正如您所看到的,当事件被标记为"跳过"时,我们只是阻止侦听器被执行。通过注册表。在第一次侦听器执行期间$worldIsExploding = true发生这种情况。

之后,我们执行所有其他侦听器并在最后通过低优先级侦听器进行清理。

最后,您还可以在具有高优先级的事件侦听器中调用$this->skippedEvents->restoreListenersForEvent($event)。即使同一个$event实例与多个Zend\EventManager\EventManagerInterface#trigger()调用一起使用,也会阻止侦听器被跳过。

答案 1 :(得分:0)

无需将自己的短路视为the event manager already provides this functionality 。您需要做的就是在事件监听器中使用$event->stopPropagation(true);

例如

public function shortCircuit(MvcEvent $e) {
    if(condition) {
        $e->stopPropagation(true);
    }
}