Zend Framework 2 отсоединяет слушателей в listeneraggregate Interface

#zend-framework2

#zend-framework2

Вопрос:

Я реализовал a 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
  );
}
  

Если какое-то условие в shortCiruit является true , остальные слушатели должны быть пропущены. Поэтому я призываю ListenerAggregateInterface::detach удалить всех моих слушателей.

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

Я ожидал, что теперь они больше не выполняются, но это не так.

Ответ №1:

Отсоединение слушателей для текущего запущенного события не будет работать во время отправки слушателей. Это связано с тем, что слушатели собираются и сортируются до их фактического выполнения, чтобы отсортировать их по их приоритету.

Остановка распространения также не будет работать с вашим текущим подходом, поскольку вы хотите отключить только определенный набор слушателей.

Однако есть решение, которое заключается в том, чтобы пропускать слушателей только при необходимости, используя небольшой реестр для записи событий, которые будут пропущены вашим агрегированным слушателем. Я написал это с головы до ног, поэтому он не тестировался, пожалуйста, тщательно протестируйте его, если хотите его использовать:

 use SplObjectStorage;
use ZendEventManagerEventInterface;

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 ZendEventManagerEventInterface;
use ZendEventManagerEventManagerInterface;
use ZendEventManagerListenerAggregateInterface;
use ZendEventManagerListenerAggregateTrait;

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 экземпляр используется с несколькими ZendEventManagerEventManagerInterface#trigger() вызовами.

Ответ №2:

Нет необходимости запускать собственное короткое замыкание, поскольку диспетчер событий уже предоставляет эту функциональность . Все, что вам нужно будет сделать, это использовать $event->stopPropagation(true); в прослушивателе событий.

Например

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

Комментарии:

1. $e->stopPropagation(true) все останавливается, и я получаю пустую страницу только с макетом, но больше ничего не заполнено. В котором говорится, что весь процесс после dispatch был отменен. Я просто хочу, чтобы мои слушатели в этом агрегате прекратили выполнение, а не все, что происходит после.

2. Возможно, отмена — это неправильный выбор слов, я хочу пропустить остальных слушателей, которые я добавил в этот агрегат.

3. старая тема, но, возможно, вы могли бы вызвать другое событие в прослушивателе событий (передать переменную mvcevent), добавить своих слушателей к этому новому событию, и вы сможете выполнить короткое замыкание, не затрагивая слушателей отправки.