#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), добавить своих слушателей к этому новому событию, и вы сможете выполнить короткое замыкание, не затрагивая слушателей отправки.