Zend Framework 2在ListenerAggregateInterface中分离监听器

Mat*_*ski 0 zend-framework2

我实现了一个ListenerAggregateInterface用于侦听dispatch(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
  );
}
Run Code Online (Sandbox Code Playgroud)

如果某些条件shortCiruittrue其余的听众应该被跳过.因此,我打电话ListenerAggregateInterface::detach删除所有听众.

public function shortCircuit(MvcEvent $e) {
  if(condition) {
    $this->detach($e->getApplication()->getEventManager());
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Ocr*_*ius 6

在调度侦听器时,为当前触发的事件分离侦听器将不起作用.这是因为侦听器在实际执行之前被收集和排序,以便按优先级对它们进行排序.

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

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

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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

(很抱歉对齐混乱,但是很难将所有内容都装在溢出中:\)

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

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

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