343 lines
9.3 KiB
PHP
343 lines
9.3 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @see https://github.com/laminas/laminas-eventmanager for the canonical source repository
|
|
* @copyright https://github.com/laminas/laminas-eventmanager/blob/master/COPYRIGHT.md
|
|
* @license https://github.com/laminas/laminas-eventmanager/blob/master/LICENSE.md New BSD License
|
|
*/
|
|
|
|
namespace Laminas\EventManager;
|
|
|
|
use ArrayObject;
|
|
|
|
/**
|
|
* Event manager: notification system
|
|
*
|
|
* Use the EventManager when you want to create a per-instance notification
|
|
* system for your objects.
|
|
*/
|
|
class EventManager implements EventManagerInterface
|
|
{
|
|
/**
|
|
* Subscribed events and their listeners
|
|
*
|
|
* STRUCTURE:
|
|
* [
|
|
* <string name> => [
|
|
* <int priority> => [
|
|
* 0 => [<callable listener>, ...]
|
|
* ],
|
|
* ...
|
|
* ],
|
|
* ...
|
|
* ]
|
|
*
|
|
* NOTE:
|
|
* This structure helps us to reuse the list of listeners
|
|
* instead of first iterating over it and generating a new one
|
|
* -> In result it improves performance by up to 25% even if it looks a bit strange
|
|
*
|
|
* @var array[]
|
|
*/
|
|
protected $events = [];
|
|
|
|
/**
|
|
* @var EventInterface Prototype to use when creating an event at trigger().
|
|
*/
|
|
protected $eventPrototype;
|
|
|
|
/**
|
|
* Identifiers, used to pull shared signals from SharedEventManagerInterface instance
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $identifiers = [];
|
|
|
|
/**
|
|
* Shared event manager
|
|
*
|
|
* @var null|SharedEventManagerInterface
|
|
*/
|
|
protected $sharedManager = null;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* Allows optionally specifying identifier(s) to use to pull signals from a
|
|
* SharedEventManagerInterface.
|
|
*
|
|
* @param SharedEventManagerInterface $sharedEventManager
|
|
* @param array $identifiers
|
|
*/
|
|
public function __construct(SharedEventManagerInterface $sharedEventManager = null, array $identifiers = [])
|
|
{
|
|
if ($sharedEventManager) {
|
|
$this->sharedManager = $sharedEventManager;
|
|
$this->setIdentifiers($identifiers);
|
|
}
|
|
|
|
$this->eventPrototype = new Event();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function setEventPrototype(EventInterface $prototype)
|
|
{
|
|
$this->eventPrototype = $prototype;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the shared event manager, if composed.
|
|
*
|
|
* @return null|SharedEventManagerInterface $sharedEventManager
|
|
*/
|
|
public function getSharedManager()
|
|
{
|
|
return $this->sharedManager;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getIdentifiers()
|
|
{
|
|
return $this->identifiers;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function setIdentifiers(array $identifiers)
|
|
{
|
|
$this->identifiers = array_unique($identifiers);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function addIdentifiers(array $identifiers)
|
|
{
|
|
$this->identifiers = array_unique(array_merge(
|
|
$this->identifiers,
|
|
$identifiers
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function trigger($eventName, $target = null, $argv = [])
|
|
{
|
|
$event = clone $this->eventPrototype;
|
|
$event->setName($eventName);
|
|
|
|
if ($target !== null) {
|
|
$event->setTarget($target);
|
|
}
|
|
|
|
if ($argv) {
|
|
$event->setParams($argv);
|
|
}
|
|
|
|
return $this->triggerListeners($event);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function triggerUntil(callable $callback, $eventName, $target = null, $argv = [])
|
|
{
|
|
$event = clone $this->eventPrototype;
|
|
$event->setName($eventName);
|
|
|
|
if ($target !== null) {
|
|
$event->setTarget($target);
|
|
}
|
|
|
|
if ($argv) {
|
|
$event->setParams($argv);
|
|
}
|
|
|
|
return $this->triggerListeners($event, $callback);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function triggerEvent(EventInterface $event)
|
|
{
|
|
return $this->triggerListeners($event);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function triggerEventUntil(callable $callback, EventInterface $event)
|
|
{
|
|
return $this->triggerListeners($event, $callback);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function attach($eventName, callable $listener, $priority = 1)
|
|
{
|
|
if (! is_string($eventName)) {
|
|
throw new Exception\InvalidArgumentException(sprintf(
|
|
'%s expects a string for the event; received %s',
|
|
__METHOD__,
|
|
(is_object($eventName) ? get_class($eventName) : gettype($eventName))
|
|
));
|
|
}
|
|
|
|
$this->events[$eventName][(int) $priority][0][] = $listener;
|
|
return $listener;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception\InvalidArgumentException for invalid event types.
|
|
*/
|
|
public function detach(callable $listener, $eventName = null, $force = false)
|
|
{
|
|
|
|
// If event is wildcard, we need to iterate through each listeners
|
|
if (null === $eventName || ('*' === $eventName && ! $force)) {
|
|
foreach (array_keys($this->events) as $eventName) {
|
|
$this->detach($listener, $eventName, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (! is_string($eventName)) {
|
|
throw new Exception\InvalidArgumentException(sprintf(
|
|
'%s expects a string for the event; received %s',
|
|
__METHOD__,
|
|
(is_object($eventName) ? get_class($eventName) : gettype($eventName))
|
|
));
|
|
}
|
|
|
|
if (! isset($this->events[$eventName])) {
|
|
return;
|
|
}
|
|
|
|
foreach ($this->events[$eventName] as $priority => $listeners) {
|
|
foreach ($listeners[0] as $index => $evaluatedListener) {
|
|
if ($evaluatedListener !== $listener) {
|
|
continue;
|
|
}
|
|
|
|
// Found the listener; remove it.
|
|
unset($this->events[$eventName][$priority][0][$index]);
|
|
|
|
// If the queue for the given priority is empty, remove it.
|
|
if (empty($this->events[$eventName][$priority][0])) {
|
|
unset($this->events[$eventName][$priority]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the queue for the given event is empty, remove it.
|
|
if (empty($this->events[$eventName])) {
|
|
unset($this->events[$eventName]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function clearListeners($eventName)
|
|
{
|
|
if (isset($this->events[$eventName])) {
|
|
unset($this->events[$eventName]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepare arguments
|
|
*
|
|
* Use this method if you want to be able to modify arguments from within a
|
|
* listener. It returns an ArrayObject of the arguments, which may then be
|
|
* passed to trigger().
|
|
*
|
|
* @param array $args
|
|
* @return ArrayObject
|
|
*/
|
|
public function prepareArgs(array $args)
|
|
{
|
|
return new ArrayObject($args);
|
|
}
|
|
|
|
/**
|
|
* Trigger listeners
|
|
*
|
|
* Actual functionality for triggering listeners, to which trigger() delegate.
|
|
*
|
|
* @param EventInterface $event
|
|
* @param null|callable $callback
|
|
* @return ResponseCollection
|
|
*/
|
|
protected function triggerListeners(EventInterface $event, callable $callback = null)
|
|
{
|
|
$name = $event->getName();
|
|
|
|
if (empty($name)) {
|
|
throw new Exception\RuntimeException('Event is missing a name; cannot trigger!');
|
|
}
|
|
|
|
if (isset($this->events[$name])) {
|
|
$listOfListenersByPriority = $this->events[$name];
|
|
|
|
if (isset($this->events['*'])) {
|
|
foreach ($this->events['*'] as $priority => $listOfListeners) {
|
|
$listOfListenersByPriority[$priority][] = $listOfListeners[0];
|
|
}
|
|
}
|
|
} elseif (isset($this->events['*'])) {
|
|
$listOfListenersByPriority = $this->events['*'];
|
|
} else {
|
|
$listOfListenersByPriority = [];
|
|
}
|
|
|
|
if ($this->sharedManager) {
|
|
foreach ($this->sharedManager->getListeners($this->identifiers, $name) as $priority => $listeners) {
|
|
$listOfListenersByPriority[$priority][] = $listeners;
|
|
}
|
|
}
|
|
|
|
// Sort by priority in reverse order
|
|
krsort($listOfListenersByPriority);
|
|
|
|
// Initial value of stop propagation flag should be false
|
|
$event->stopPropagation(false);
|
|
|
|
// Execute listeners
|
|
$responses = new ResponseCollection();
|
|
foreach ($listOfListenersByPriority as $listOfListeners) {
|
|
foreach ($listOfListeners as $listeners) {
|
|
foreach ($listeners as $listener) {
|
|
$response = $listener($event);
|
|
$responses->push($response);
|
|
|
|
// If the event was asked to stop propagating, do so
|
|
if ($event->propagationIsStopped()) {
|
|
$responses->setStopped(true);
|
|
return $responses;
|
|
}
|
|
|
|
// If the result causes our validation callback to return true,
|
|
// stop propagation
|
|
if ($callback && $callback($response)) {
|
|
$responses->setStopped(true);
|
|
return $responses;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $responses;
|
|
}
|
|
}
|