Augmentation vers version 3.3.0

This commit is contained in:
Gauvain Boiché
2020-03-31 15:31:03 +02:00
parent d926806907
commit a1864c0414
2618 changed files with 406015 additions and 31377 deletions

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -15,9 +15,9 @@ namespace Zend\EventManager;
abstract class AbstractListenerAggregate implements ListenerAggregateInterface
{
/**
* @var \Zend\Stdlib\CallbackHandler[]
* @var callable[]
*/
protected $listeners = array();
protected $listeners = [];
/**
* {@inheritDoc}
@@ -25,9 +25,8 @@ abstract class AbstractListenerAggregate implements ListenerAggregateInterface
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $callback) {
if ($events->detach($callback)) {
unset($this->listeners[$index]);
}
$events->detach($callback);
unset($this->listeners[$index]);
}
}
}

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -32,7 +32,7 @@ class Event implements EventInterface
/**
* @var array|ArrayAccess|object The event parameters
*/
protected $params = array();
protected $params = [];
/**
* @var bool Whether or not to stop propagation
@@ -91,19 +91,17 @@ class Event implements EventInterface
* Overwrites parameters
*
* @param array|ArrayAccess|object $params
* @return Event
* @throws Exception\InvalidArgumentException
*/
public function setParams($params)
{
if (!is_array($params) && !is_object($params)) {
if (! is_array($params) && ! is_object($params)) {
throw new Exception\InvalidArgumentException(
sprintf('Event parameters must be an array or object; received "%s"', gettype($params))
);
}
$this->params = $params;
return $this;
}
/**
@@ -129,7 +127,7 @@ class Event implements EventInterface
{
// Check in params that are arrays or implement array access
if (is_array($this->params) || $this->params instanceof ArrayAccess) {
if (!isset($this->params[$name])) {
if (! isset($this->params[$name])) {
return $default;
}
@@ -137,7 +135,7 @@ class Event implements EventInterface
}
// Check in normal objects
if (!isset($this->params->{$name})) {
if (! isset($this->params->{$name})) {
return $default;
}
return $this->params->{$name};
@@ -147,24 +145,20 @@ class Event implements EventInterface
* Set the event name
*
* @param string $name
* @return Event
*/
public function setName($name)
{
$this->name = (string) $name;
return $this;
}
/**
* Set the event target/context
*
* @param null|string|object $target
* @return Event
*/
public function setTarget($target)
{
$this->target = $target;
return $this;
}
/**
@@ -172,25 +166,23 @@ class Event implements EventInterface
*
* @param string|int $name
* @param mixed $value
* @return Event
*/
public function setParam($name, $value)
{
if (is_array($this->params) || $this->params instanceof ArrayAccess) {
// Arrays or objects implementing array access
$this->params[$name] = $value;
} else {
// Objects
$this->params->{$name} = $value;
return;
}
return $this;
// Objects
$this->params->{$name} = $value;
}
/**
* Stop further event propagation
*
* @param bool $flag
* @return void
*/
public function stopPropagation($flag = true)
{

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -65,7 +65,7 @@ interface EventInterface
/**
* Set event parameters
*
* @param string $params
* @param array|ArrayAccess $params
* @return void
*/
public function setParams($params);

View File

@@ -2,18 +2,14 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use ArrayAccess;
use ArrayObject;
use Traversable;
use Zend\Stdlib\CallbackHandler;
use Zend\Stdlib\PriorityQueue;
/**
* Event manager: notification system
@@ -25,24 +21,43 @@ class EventManager implements EventManagerInterface
{
/**
* Subscribed events and their listeners
* @var array Array of PriorityQueue objects
*
* 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 = array();
protected $events = [];
/**
* @var string Class representing the event being emitted
* @var EventInterface Prototype to use when creating an event at trigger().
*/
protected $eventClass = 'Zend\EventManager\Event';
protected $eventPrototype;
/**
* Identifiers, used to pull shared signals from SharedEventManagerInterface instance
*
* @var array
*/
protected $identifiers = array();
protected $identifiers = [];
/**
* Shared event manager
* @var false|null|SharedEventManagerInterface
*
* @var null|SharedEventManagerInterface
*/
protected $sharedManager = null;
@@ -52,80 +67,39 @@ class EventManager implements EventManagerInterface
* Allows optionally specifying identifier(s) to use to pull signals from a
* SharedEventManagerInterface.
*
* @param null|string|int|array|Traversable $identifiers
*/
public function __construct($identifiers = null)
{
$this->setIdentifiers($identifiers);
}
/**
* Set the event class to utilize
*
* @param string $class
* @return EventManager
*/
public function setEventClass($class)
{
$this->eventClass = $class;
return $this;
}
/**
* Set shared event manager
*
* @param SharedEventManagerInterface $sharedEventManager
* @return EventManager
* @param array $identifiers
*/
public function setSharedManager(SharedEventManagerInterface $sharedEventManager)
public function __construct(SharedEventManagerInterface $sharedEventManager = null, array $identifiers = [])
{
$this->sharedManager = $sharedEventManager;
StaticEventManager::setInstance($sharedEventManager);
return $this;
if ($sharedEventManager) {
$this->sharedManager = $sharedEventManager;
$this->setIdentifiers($identifiers);
}
$this->eventPrototype = new Event();
}
/**
* Remove any shared event manager currently attached
*
* @return void
* @inheritDoc
*/
public function unsetSharedManager()
public function setEventPrototype(EventInterface $prototype)
{
$this->sharedManager = false;
$this->eventPrototype = $prototype;
}
/**
* Get shared event manager
* Retrieve the shared event manager, if composed.
*
* If one is not defined, but we have a static instance in
* StaticEventManager, that one will be used and set in this instance.
*
* If none is available in the StaticEventManager, a boolean false is
* returned.
*
* @return false|SharedEventManagerInterface
* @return null|SharedEventManagerInterface $sharedEventManager
*/
public function getSharedManager()
{
// "false" means "I do not want a shared manager; don't try and fetch one"
if (false === $this->sharedManager
|| $this->sharedManager instanceof SharedEventManagerInterface
) {
return $this->sharedManager;
}
if (!StaticEventManager::hasInstance()) {
return false;
}
$this->sharedManager = StaticEventManager::getInstance();
return $this->sharedManager;
}
/**
* Get the identifier(s) for this EventManager
*
* @return array
* @inheritDoc
*/
public function getIdentifiers()
{
@@ -133,258 +107,152 @@ class EventManager implements EventManagerInterface
}
/**
* Set the identifiers (overrides any currently set identifiers)
*
* @param string|int|array|Traversable $identifiers
* @return EventManager Provides a fluent interface
* @inheritDoc
*/
public function setIdentifiers($identifiers)
public function setIdentifiers(array $identifiers)
{
if (is_array($identifiers) || $identifiers instanceof Traversable) {
$this->identifiers = array_unique((array) $identifiers);
} elseif ($identifiers !== null) {
$this->identifiers = array($identifiers);
}
return $this;
$this->identifiers = array_unique($identifiers);
}
/**
* Add some identifier(s) (appends to any currently set identifiers)
*
* @param string|int|array|Traversable $identifiers
* @return EventManager Provides a fluent interface
* @inheritDoc
*/
public function addIdentifiers($identifiers)
public function addIdentifiers(array $identifiers)
{
if (is_array($identifiers) || $identifiers instanceof Traversable) {
$this->identifiers = array_unique(array_merge($this->identifiers, (array) $identifiers));
} elseif ($identifiers !== null) {
$this->identifiers = array_unique(array_merge($this->identifiers, array($identifiers)));
}
return $this;
$this->identifiers = array_unique(array_merge(
$this->identifiers,
$identifiers
));
}
/**
* Trigger all listeners for a given event
*
* @param string|EventInterface $event
* @param string|object $target Object calling emit, or symbol describing target (such as static method name)
* @param array|ArrayAccess $argv Array of arguments; typically, should be associative
* @param null|callable $callback Trigger listeners until return value of this callback evaluate to true
* @return ResponseCollection All listener return values
* @throws Exception\InvalidCallbackException
* @inheritDoc
*/
public function trigger($event, $target = null, $argv = array(), $callback = null)
public function trigger($eventName, $target = null, $argv = [])
{
if ($event instanceof EventInterface) {
$e = $event;
$event = $e->getName();
$callback = $target;
} elseif ($target instanceof EventInterface) {
$e = $target;
$e->setName($event);
$callback = $argv;
} elseif ($argv instanceof EventInterface) {
$e = $argv;
$e->setName($event);
$e->setTarget($target);
} else {
$e = new $this->eventClass();
$e->setName($event);
$e->setTarget($target);
$e->setParams($argv);
$event = clone $this->eventPrototype;
$event->setName($eventName);
if ($target !== null) {
$event->setTarget($target);
}
if ($callback && !is_callable($callback)) {
throw new Exception\InvalidCallbackException('Invalid callback provided');
if ($argv) {
$event->setParams($argv);
}
// Initial value of stop propagation flag should be false
$e->stopPropagation(false);
return $this->triggerListeners($event, $e, $callback);
return $this->triggerListeners($event);
}
/**
* Trigger listeners until return value of one causes a callback to
* evaluate to true
*
* Triggers listeners until the provided callback evaluates the return
* value of one as true, or until all listeners have been executed.
*
* @param string|EventInterface $event
* @param string|object $target Object calling emit, or symbol describing target (such as static method name)
* @param array|ArrayAccess $argv Array of arguments; typically, should be associative
* @param callable $callback
* @return ResponseCollection
* @deprecated Please use trigger()
* @throws Exception\InvalidCallbackException if invalid callable provided
* @inheritDoc
*/
public function triggerUntil($event, $target, $argv = null, $callback = null)
public function triggerUntil(callable $callback, $eventName, $target = null, $argv = [])
{
trigger_error(
'This method is deprecated and will be removed in the future. Please use trigger() instead.',
E_USER_DEPRECATED
);
return $this->trigger($event, $target, $argv, $callback);
$event = clone $this->eventPrototype;
$event->setName($eventName);
if ($target !== null) {
$event->setTarget($target);
}
if ($argv) {
$event->setParams($argv);
}
return $this->triggerListeners($event, $callback);
}
/**
* Attach a listener to an event
*
* The first argument is the event, and the next argument describes a
* callback that will respond to that event. A CallbackHandler instance
* describing the event listener combination will be returned.
*
* The last argument indicates a priority at which the event should be
* executed. By default, this value is 1; however, you may set it for any
* integer value. Higher values have higher priority (i.e., execute first).
*
* You can specify "*" for the event name. In such cases, the listener will
* be triggered for every event.
*
* @param string|array|ListenerAggregateInterface $event An event or array of event names. If a ListenerAggregateInterface, proxies to {@link attachAggregate()}.
* @param callable|int $callback If string $event provided, expects PHP callback; for a ListenerAggregateInterface $event, this will be the priority
* @param int $priority If provided, the priority at which to register the callable
* @return CallbackHandler|mixed CallbackHandler if attaching callable (to allow later unsubscribe); mixed if attaching aggregate
* @throws Exception\InvalidArgumentException
* @inheritDoc
*/
public function attach($event, $callback = null, $priority = 1)
public function triggerEvent(EventInterface $event)
{
// Proxy ListenerAggregateInterface arguments to attachAggregate()
if ($event instanceof ListenerAggregateInterface) {
return $this->attachAggregate($event, $callback);
}
return $this->triggerListeners($event);
}
// Null callback is invalid
if (null === $callback) {
/**
* @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 callback; none provided',
__METHOD__
'%s expects a string for the event; received %s',
__METHOD__,
(is_object($eventName) ? get_class($eventName) : gettype($eventName))
));
}
// Array of events should be registered individually, and return an array of all listeners
if (is_array($event)) {
$listeners = array();
foreach ($event as $name) {
$listeners[] = $this->attach($name, $callback, $priority);
}
return $listeners;
}
// If we don't have a priority queue for the event yet, create one
if (empty($this->events[$event])) {
$this->events[$event] = new PriorityQueue();
}
// Create a callback handler, setting the event and priority in its metadata
$listener = new CallbackHandler($callback, array('event' => $event, 'priority' => $priority));
// Inject the callback handler into the queue
$this->events[$event]->insert($listener, $priority);
$this->events[$eventName][(int) $priority][0][] = $listener;
return $listener;
}
/**
* Attach a listener aggregate
*
* Listener aggregates accept an EventManagerInterface instance, and call attach()
* one or more times, typically to attach to multiple events using local
* methods.
*
* @param ListenerAggregateInterface $aggregate
* @param int $priority If provided, a suggested priority for the aggregate to use
* @return mixed return value of {@link ListenerAggregateInterface::attach()}
* @inheritDoc
* @throws Exception\InvalidArgumentException for invalid event types.
*/
public function attachAggregate(ListenerAggregateInterface $aggregate, $priority = 1)
public function detach(callable $listener, $eventName = null, $force = false)
{
return $aggregate->attach($this, $priority);
}
/**
* Unsubscribe a listener from an event
*
* @param CallbackHandler|ListenerAggregateInterface $listener
* @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
* @throws Exception\InvalidArgumentException if invalid listener provided
*/
public function detach($listener)
{
if ($listener instanceof ListenerAggregateInterface) {
return $this->detachAggregate($listener);
// 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 (!$listener instanceof CallbackHandler) {
if (! is_string($eventName)) {
throw new Exception\InvalidArgumentException(sprintf(
'%s: expected a ListenerAggregateInterface or CallbackHandler; received "%s"',
'%s expects a string for the event; received %s',
__METHOD__,
(is_object($listener) ? get_class($listener) : gettype($listener))
(is_object($eventName) ? get_class($eventName) : gettype($eventName))
));
}
$event = $listener->getMetadatum('event');
if (!$event || empty($this->events[$event])) {
return false;
if (! isset($this->events[$eventName])) {
return;
}
$return = $this->events[$event]->remove($listener);
if (!$return) {
return false;
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 (!count($this->events[$event])) {
unset($this->events[$event]);
// If the queue for the given event is empty, remove it.
if (empty($this->events[$eventName])) {
unset($this->events[$eventName]);
}
return true;
}
/**
* Detach a listener aggregate
*
* Listener aggregates accept an EventManagerInterface instance, and call detach()
* of all previously attached listeners.
*
* @param ListenerAggregateInterface $aggregate
* @return mixed return value of {@link ListenerAggregateInterface::detach()}
* @inheritDoc
*/
public function detachAggregate(ListenerAggregateInterface $aggregate)
public function clearListeners($eventName)
{
return $aggregate->detach($this);
}
/**
* Retrieve all registered events
*
* @return array
*/
public function getEvents()
{
return array_keys($this->events);
}
/**
* Retrieve all listeners for a given event
*
* @param string $event
* @return PriorityQueue
*/
public function getListeners($event)
{
if (!array_key_exists($event, $this->events)) {
return new PriorityQueue();
}
return $this->events[$event];
}
/**
* Clear all listeners for a given event
*
* @param string $event
* @return void
*/
public function clearListeners($event)
{
if (!empty($this->events[$event])) {
unset($this->events[$event]);
if (isset($this->events[$eventName])) {
unset($this->events[$eventName]);
}
}
@@ -408,119 +276,68 @@ class EventManager implements EventManagerInterface
*
* Actual functionality for triggering listeners, to which trigger() delegate.
*
* @param string $event Event name
* @param EventInterface $e
* @param null|callable $callback
* @param EventInterface $event
* @param null|callable $callback
* @return ResponseCollection
*/
protected function triggerListeners($event, EventInterface $e, $callback = null)
protected function triggerListeners(EventInterface $event, callable $callback = null)
{
$responses = new ResponseCollection;
$listeners = $this->getListeners($event);
$name = $event->getName();
// Add shared/wildcard listeners to the list of listeners,
// but don't modify the listeners object
$sharedListeners = $this->getSharedListeners($event);
$sharedWildcardListeners = $this->getSharedListeners('*');
$wildcardListeners = $this->getListeners('*');
if (count($sharedListeners) || count($sharedWildcardListeners) || count($wildcardListeners)) {
$listeners = clone $listeners;
// Shared listeners on this specific event
$this->insertListeners($listeners, $sharedListeners);
// Shared wildcard listeners
$this->insertListeners($listeners, $sharedWildcardListeners);
// Add wildcard listeners
$this->insertListeners($listeners, $wildcardListeners);
if (empty($name)) {
throw new Exception\RuntimeException('Event is missing a name; cannot trigger!');
}
foreach ($listeners as $listener) {
$listenerCallback = $listener->getCallback();
if (isset($this->events[$name])) {
$listOfListenersByPriority = $this->events[$name];
// Trigger the listener's callback, and push its result onto the
// response collection
$responses->push(call_user_func($listenerCallback, $e));
// If the event was asked to stop propagating, do so
if ($e->propagationIsStopped()) {
$responses->setStopped(true);
break;
if (isset($this->events['*'])) {
foreach ($this->events['*'] as $priority => $listOfListeners) {
$listOfListenersByPriority[$priority][] = $listOfListeners[0];
}
}
} elseif (isset($this->events['*'])) {
$listOfListenersByPriority = $this->events['*'];
} else {
$listOfListenersByPriority = [];
}
// If the result causes our validation callback to return true,
// stop propagation
if ($callback && call_user_func($callback, $responses->last())) {
$responses->setStopped(true);
break;
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;
}
/**
* Get list of all listeners attached to the shared event manager for
* identifiers registered by this instance
*
* @param string $event
* @return array
*/
protected function getSharedListeners($event)
{
if (!$sharedManager = $this->getSharedManager()) {
return array();
}
$identifiers = $this->getIdentifiers();
//Add wildcard id to the search, if not already added
if (!in_array('*', $identifiers)) {
$identifiers[] = '*';
}
$sharedListeners = array();
foreach ($identifiers as $id) {
if (!$listeners = $sharedManager->getListeners($id, $event)) {
continue;
}
if (!is_array($listeners) && !($listeners instanceof Traversable)) {
continue;
}
foreach ($listeners as $listener) {
if (!$listener instanceof CallbackHandler) {
continue;
}
$sharedListeners[] = $listener;
}
}
return $sharedListeners;
}
/**
* Add listeners to the master queue of listeners
*
* Used to inject shared listeners and wildcard listeners.
*
* @param PriorityQueue $masterListeners
* @param array|Traversable $listeners
* @return void
*/
protected function insertListeners($masterListeners, $listeners)
{
foreach ($listeners as $listener) {
$priority = $listener->getMetadatum('priority');
if (null === $priority) {
$priority = 1;
} elseif (is_array($priority)) {
// If we have an array, likely using PriorityQueue. Grab first
// element of the array, as that's the actual priority.
$priority = array_shift($priority);
}
$masterListeners->insert($listener, $priority);
}
}
}

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -36,11 +36,10 @@ trait EventManagerAwareTrait
* $this->eventIdentifier property.
*
* @param EventManagerInterface $events
* @return mixed
*/
public function setEventManager(EventManagerInterface $events)
{
$identifiers = array(__CLASS__, get_class($this));
$identifiers = [__CLASS__, get_class($this)];
if (isset($this->eventIdentifier)) {
if ((is_string($this->eventIdentifier))
|| (is_array($this->eventIdentifier))
@@ -57,7 +56,6 @@ trait EventManagerAwareTrait
if (method_exists($this, 'attachDefaultListeners')) {
$this->attachDefaultListeners();
}
return $this;
}
/**
@@ -69,7 +67,7 @@ trait EventManagerAwareTrait
*/
public function getEventManager()
{
if (!$this->events instanceof EventManagerInterface) {
if (! $this->events instanceof EventManagerInterface) {
$this->setEventManager(new EventManager());
}
return $this->events;

View File

@@ -2,105 +2,140 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Traversable;
use Zend\Stdlib\CallbackHandler;
/**
* Interface for messengers
*/
interface EventManagerInterface extends SharedEventManagerAwareInterface
interface EventManagerInterface extends SharedEventsCapableInterface
{
/**
* Create and trigger an event.
*
* Use this method when you do not want to create an EventInterface
* instance prior to triggering. You will be required to pass:
*
* - the event name
* - the event target (can be null)
* - any event parameters you want to provide (empty array by default)
*
* It will create the Event instance for you and then trigger all listeners
* related to the event.
*
* @param string $eventName
* @param null|object|string $target
* @param array|object $argv
* @return ResponseCollection
*/
public function trigger($eventName, $target = null, $argv = []);
/**
* Create and trigger an event, applying a callback to each listener result.
*
* Use this method when you do not want to create an EventInterface
* instance prior to triggering. You will be required to pass:
*
* - the event name
* - the event target (can be null)
* - any event parameters you want to provide (empty array by default)
*
* It will create the Event instance for you, and trigger all listeners
* related to the event.
*
* The result of each listener is passed to $callback; if $callback returns
* a boolean true value, the manager must short-circuit listener execution.
*
* @param callable $callback
* @param string $eventName
* @param null|object|string $target
* @param array|object $argv
* @return ResponseCollection
*/
public function triggerUntil(callable $callback, $eventName, $target = null, $argv = []);
/**
* Trigger an event
*
* Should allow handling the following scenarios:
* - Passing Event object only
* - Passing event name and Event object only
* - Passing event name, target, and Event object
* - Passing event name, target, and array|ArrayAccess of arguments
* - Passing event name, target, array|ArrayAccess of arguments, and callback
* Provided an EventInterface instance, this method will trigger listeners
* based on the event name, raising an exception if the event name is missing.
*
* @param string|EventInterface $event
* @param object|string $target
* @param array|object $argv
* @param null|callable $callback
* @param EventInterface $event
* @return ResponseCollection
*/
public function trigger($event, $target = null, $argv = array(), $callback = null);
public function triggerEvent(EventInterface $event);
/**
* Trigger an event until the given callback returns a boolean true
* Trigger an event, applying a callback to each listener result.
*
* Should allow handling the following scenarios:
* - Passing Event object and callback only
* - Passing event name, Event object, and callback only
* - Passing event name, target, Event object, and callback
* - Passing event name, target, array|ArrayAccess of arguments, and callback
* Provided an EventInterface instance, this method will trigger listeners
* based on the event name, raising an exception if the event name is missing.
*
* The result of each listener is passed to $callback; if $callback returns
* a boolean true value, the manager must short-circuit listener execution.
*
* @param string|EventInterface $event
* @param object|string $target
* @param array|object $argv
* @param callable $callback
* @param EventInterface $event
* @return ResponseCollection
* @deprecated Please use trigger()
*/
public function triggerUntil($event, $target, $argv = null, $callback = null);
public function triggerEventUntil(callable $callback, EventInterface $event);
/**
* Attach a listener to an event
*
* @param string $event
* @param callable $callback
* @param int $priority Priority at which to register listener
* @return CallbackHandler
* The first argument is the event, and the next argument is a
* callable that will respond to that event.
*
* The last argument indicates a priority at which the event should be
* executed; by default, this value is 1; however, you may set it for any
* integer value. Higher values have higher priority (i.e., execute first).
*
* You can specify "*" for the event name. In such cases, the listener will
* be triggered for every event *that has registered listeners at the time
* it is attached*. As such, register wildcard events last whenever possible!
*
* @param string $eventName Event to which to listen.
* @param callable $listener
* @param int $priority Priority at which to register listener.
* @return callable
*/
public function attach($event, $callback = null, $priority = 1);
public function attach($eventName, callable $listener, $priority = 1);
/**
* Detach an event listener
* Detach a listener.
*
* @param CallbackHandler|ListenerAggregateInterface $listener
* @return bool
*/
public function detach($listener);
/**
* Get a list of events for which this collection has listeners
* If no $event or '*' is provided, detaches listener from all events;
* otherwise, detaches only from the named event.
*
* @return array
* @param callable $listener
* @param null|string $eventName Event from which to detach; null and '*'
* indicate all events.
* @return void
*/
public function getEvents();
/**
* Retrieve a list of listeners registered to a given event
*
* @param string $event
* @return array|object
*/
public function getListeners($event);
public function detach(callable $listener, $eventName = null);
/**
* Clear all listeners for a given event
*
* @param string $event
* @param string $eventName
* @return void
*/
public function clearListeners($event);
public function clearListeners($eventName);
/**
* Set the event class to utilize
* Provide an event prototype to use with trigger().
*
* @param string $class
* @return EventManagerInterface
* When `trigger()` needs to create an event instance, it should clone the
* prototype provided to this method.
*
* @param EventInterface $prototype
* @return void
*/
public function setEventClass($class);
public function setEventPrototype(EventInterface $prototype);
/**
* Get the identifier(s) for this EventManager
@@ -112,33 +147,16 @@ interface EventManagerInterface extends SharedEventManagerAwareInterface
/**
* Set the identifiers (overrides any currently set identifiers)
*
* @param string|int|array|Traversable $identifiers
* @return EventManagerInterface
* @param string[] $identifiers
* @return void
*/
public function setIdentifiers($identifiers);
public function setIdentifiers(array $identifiers);
/**
* Add some identifier(s) (appends to any currently set identifiers)
* Add identifier(s) (appends to any currently set identifiers)
*
* @param string|int|array|Traversable $identifiers
* @return EventManagerInterface
* @param string[] $identifiers
* @return void
*/
public function addIdentifiers($identifiers);
/**
* Attach a listener aggregate
*
* @param ListenerAggregateInterface $aggregate
* @param int $priority If provided, a suggested priority for the aggregate to use
* @return mixed return value of {@link ListenerAggregateInterface::attach()}
*/
public function attachAggregate(ListenerAggregateInterface $aggregate, $priority = 1);
/**
* Detach a listener aggregate
*
* @param ListenerAggregateInterface $aggregate
* @return mixed return value of {@link ListenerAggregateInterface::detach()}
*/
public function detachAggregate(ListenerAggregateInterface $aggregate);
public function addIdentifiers(array $identifiers);
}

View File

@@ -2,15 +2,15 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
/**
* Interface providing events that can be attached, detached and triggered.
* Interface indicating that an object composes an EventManagerInterface instance.
*/
interface EventsCapableInterface
{

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Exception;

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Exception;

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Exception;

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Exception;

View File

@@ -0,0 +1,16 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Exception;
use RuntimeException as SplRuntimeException;
class RuntimeException extends SplRuntimeException implements ExceptionInterface
{
}

View File

@@ -2,15 +2,14 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Filter;
use Zend\EventManager\ResponseCollection;
use Zend\Stdlib\CallbackHandler;
/**
* Interface for intercepting filter chains
@@ -24,23 +23,22 @@ interface FilterInterface
* @param array $params
* @return mixed
*/
public function run($context, array $params = array());
public function run($context, array $params = []);
/**
* Attach an intercepting filter
*
* @param callable $callback
* @return CallbackHandler
*/
public function attach($callback);
public function attach(callable $callback);
/**
* Detach an intercepting filter
*
* @param CallbackHandler $filter
* @param callable $filter
* @return bool
*/
public function detach(CallbackHandler $filter);
public function detach(callable $filter);
/**
* Get all intercepting filters

View File

@@ -2,15 +2,15 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Filter;
use Zend\Stdlib\CallbackHandler;
use Zend\Stdlib\SplPriorityQueue;
use Zend\EventManager\Exception;
use Zend\Stdlib\FastPriorityQueue;
/**
* Specialized priority queue implementation for use with an intercepting
@@ -18,7 +18,7 @@ use Zend\Stdlib\SplPriorityQueue;
*
* Allows removal
*/
class FilterIterator extends SplPriorityQueue
class FilterIterator extends FastPriorityQueue
{
/**
* Does the queue contain a given value?
@@ -28,8 +28,7 @@ class FilterIterator extends SplPriorityQueue
*/
public function contains($datum)
{
$chain = clone $this;
foreach ($chain as $item) {
foreach ($this as $item) {
if ($item === $datum) {
return true;
}
@@ -37,6 +36,28 @@ class FilterIterator extends SplPriorityQueue
return false;
}
/**
* Insert a value into the queue.
*
* Requires a callable.
*
* @param callable $value
* @param mixed $priority
* @return void
* @throws Exception\InvalidArgumentException for non-callable $value.
*/
public function insert($value, $priority)
{
if (! is_callable($value)) {
throw new Exception\InvalidArgumentException(sprintf(
'%s can only aggregate callables; received %s',
__CLASS__,
(is_object($value) ? get_class($value) : gettype($value))
));
}
parent::insert($value, $priority);
}
/**
* Remove a value from the queue
*
@@ -52,9 +73,9 @@ class FilterIterator extends SplPriorityQueue
// Iterate and remove any matches
$removed = false;
$items = array();
$items = [];
$this->rewind();
while (!$this->isEmpty()) {
while (! $this->isEmpty()) {
$item = $this->extract();
if ($item['data'] === $datum) {
$removed = true;
@@ -82,18 +103,18 @@ class FilterIterator extends SplPriorityQueue
* @param FilterIterator $chain
* @return mixed
*/
public function next($context = null, array $params = array(), $chain = null)
public function next($context = null, array $params = [], $chain = null)
{
if (empty($context) || $chain->isEmpty()) {
if (empty($context) || ($chain instanceof FilterIterator && $chain->isEmpty())) {
return;
}
//We can't extract from an empty heap
if ($this->isEmpty()) {
return;
}
$next = $this->extract();
if (!$next instanceof CallbackHandler) {
return;
}
$return = call_user_func($next->getCallback(), $context, $params, $chain);
return $return;
return $next($context, $params, $chain);
}
}

View File

@@ -2,15 +2,13 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Zend\Stdlib\CallbackHandler;
/**
* FilterChain: intercepting filter manager
*/
@@ -40,7 +38,7 @@ class FilterChain implements Filter\FilterInterface
* @param mixed $argv Associative array of arguments
* @return mixed
*/
public function run($context, array $argv = array())
public function run($context, array $argv = [])
{
$chain = clone $this->getFilters();
@@ -49,38 +47,32 @@ class FilterChain implements Filter\FilterInterface
}
$next = $chain->extract();
if (!$next instanceof CallbackHandler) {
return;
}
return call_user_func($next->getCallback(), $context, $argv, $chain);
return $next($context, $argv, $chain);
}
/**
* Connect a filter to the chain
*
* @param callable $callback PHP Callback
* @param int $priority Priority in the queue at which to execute; defaults to 1 (higher numbers == higher priority)
* @param int $priority Priority in the queue at which to execute;
* defaults to 1 (higher numbers == higher priority)
* @return CallbackHandler (to allow later unsubscribe)
* @throws Exception\InvalidCallbackException
*/
public function attach($callback, $priority = 1)
public function attach(callable $callback, $priority = 1)
{
if (empty($callback)) {
throw new Exception\InvalidCallbackException('No callback provided');
}
$filter = new CallbackHandler($callback, array('priority' => $priority));
$this->filters->insert($filter, $priority);
return $filter;
$this->filters->insert($callback, $priority);
return $callback;
}
/**
* Detach a filter from the chain
*
* @param CallbackHandler $filter
* @param callable $filter
* @return bool Returns true if filter found and unsubscribed; returns false otherwise
*/
public function detach(CallbackHandler $filter)
public function detach(callable $filter)
{
return $this->filters->remove($filter);
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Interop\Container\ContainerInterface;
/**
* Lazy listener instance for use with LazyListenerAggregate.
*
* Used as an internal class for the LazyAggregate to allow lazy creation of
* listeners via a dependency injection container.
*
* Lazy event listener definitions add the following members to what the
* LazyListener accepts:
*
* - event: the event name to attach to.
* - priority: the priority at which to attach the listener, if not the default.
*/
class LazyEventListener extends LazyListener
{
/**
* @var string Event name to which to attach.
*/
private $event;
/**
* @var null|int Priority at which to attach.
*/
private $priority;
/**
* @param array $definition
* @param ContainerInterface $container
* @param array $env
*/
public function __construct(array $definition, ContainerInterface $container, array $env = [])
{
parent::__construct($definition, $container, $env);
if ((! isset($definition['event'])
|| ! is_string($definition['event'])
|| empty($definition['event']))
) {
throw new Exception\InvalidArgumentException(
'Lazy listener definition is missing a valid "event" member; cannot create LazyListener'
);
}
$this->event = $definition['event'];
$this->priority = isset($definition['priority']) ? (int) $definition['priority'] : null;
}
/**
* @return string
*/
public function getEvent()
{
return $this->event;
}
/**
* @return int
*/
public function getPriority($default = 1)
{
return (null !== $this->priority) ? $this->priority : $default;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Interop\Container\ContainerInterface;
/**
* Lazy listener instance.
*
* Used to allow lazy creation of listeners via a dependency injection
* container.
*
* Lazy listener definitions have the following members:
*
* - listener: the service name of the listener to use.
* - method: the method name of the listener to invoke for the specified event.
*
* If desired, you can pass $env at instantiation; this will be passed to the
* container's `build()` method, if it has one, when creating the listener
* instance.
*
* Pass instances directly to the event manager's `attach()` method as the
* listener argument.
*/
class LazyListener
{
/**
* @var ContainerInterface Container from which to pull listener.
*/
private $container;
/**
* @var array Variables/options to use during service creation, if any.
*/
private $env;
/**
* @var callable Marshaled listener callback.
*/
private $listener;
/**
* @var string Method name to invoke on listener.
*/
private $method;
/**
* @var string Service name of listener.
*/
private $service;
/**
* @param array $definition
* @param ContainerInterface $container
* @param array $env
*/
public function __construct(array $definition, ContainerInterface $container, array $env = [])
{
if ((! isset($definition['listener'])
|| ! is_string($definition['listener'])
|| empty($definition['listener']))
) {
throw new Exception\InvalidArgumentException(
'Lazy listener definition is missing a valid "listener" member; cannot create LazyListener'
);
}
if ((! isset($definition['method'])
|| ! is_string($definition['method'])
|| empty($definition['method']))
) {
throw new Exception\InvalidArgumentException(
'Lazy listener definition is missing a valid "method" member; cannot create LazyListener'
);
}
$this->service = $definition['listener'];
$this->method = $definition['method'];
$this->container = $container;
$this->env = $env;
}
/**
* Use the listener as an invokable, allowing direct attachment to an event manager.
*
* @param EventInterface $event
* @return callable
*/
public function __invoke(EventInterface $event)
{
$listener = $this->fetchListener();
$method = $this->method;
return $listener->{$method}($event);
}
/**
* @return callable
*/
private function fetchListener()
{
if ($this->listener) {
return $this->listener;
}
// In the future, typehint against Zend\ServiceManager\ServiceLocatorInterface,
// which defines this message starting in v3.
if (method_exists($this->container, 'build') && ! empty($this->env)) {
$this->listener = $this->container->build($this->service, $this->env);
return $this->listener;
}
$this->listener = $this->container->get($this->service);
return $this->listener;
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Interop\Container\ContainerInterface;
/**
* Aggregate listener for attaching lazy listeners.
*
* Lazy listeners are listeners where creation is deferred until they are
* triggered; this removes the most costly mechanism of pulling a listener
* from a container unless the listener is actually invoked.
*
* Usage is:
*
* <code>
* $events->attachAggregate(new LazyListenerAggregate(
* $lazyEventListenersOrDefinitions,
* $container
* ));
* </code>
*/
class LazyListenerAggregate implements ListenerAggregateInterface
{
use ListenerAggregateTrait;
/**
* @var ContainerInterface Container from which to pull lazy listeners.
*/
private $container;
/**
* @var array Additional environment/option variables to use when creating listener.
*/
private $env;
/**
* Generated LazyEventListener instances.
*
* @var LazyEventListener[]
*/
private $lazyListeners = [];
/**
* Constructor
*
* Accepts the composed $listeners, as well as the $container and $env in
* order to create a listener aggregate that defers listener creation until
* the listener is triggered.
*
* Listeners may be either LazyEventListener instances, or lazy event
* listener definitions that can be provided to a LazyEventListener
* constructor in order to create a new instance; in the latter case, the
* $container and $env will be passed at instantiation as well.
*
* @var array $listeners LazyEventListener instances or array definitions
* to pass to the LazyEventListener constructor.
* @var ContainerInterface $container
* @var array $env
* @throws Exception\InvalidArgumentException for invalid listener items.
*/
public function __construct(array $listeners, ContainerInterface $container, array $env = [])
{
$this->container = $container;
$this->env = $env;
// This would raise an exception for invalid structs
foreach ($listeners as $listener) {
if (is_array($listener)) {
$listener = new LazyEventListener($listener, $container, $env);
}
if (! $listener instanceof LazyEventListener) {
throw new Exception\InvalidArgumentException(sprintf(
'All listeners must be LazyEventListener instances or definitions; received %s',
(is_object($listener) ? get_class($listener) : gettype($listener))
));
}
$this->lazyListeners[] = $listener;
}
}
/**
* Attach the aggregate to the event manager.
*
* Loops through all composed lazy listeners, and attaches them to the
* event manager.
*
* @var EventManagerInterface $events
* @var int $priority
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
foreach ($this->lazyListeners as $lazyListener) {
$this->listeners[] = $events->attach(
$lazyListener->getEvent(),
$lazyListener,
$lazyListener->getPriority($priority)
);
}
}
}

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -26,16 +26,15 @@ interface ListenerAggregateInterface
* implementation will pass this to the aggregate.
*
* @param EventManagerInterface $events
*
* @param int $priority
* @return void
*/
public function attach(EventManagerInterface $events);
public function attach(EventManagerInterface $events, $priority = 1);
/**
* Detach all previously attached listeners
*
* @param EventManagerInterface $events
*
* @return void
*/
public function detach(EventManagerInterface $events);

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -16,9 +16,9 @@ namespace Zend\EventManager;
trait ListenerAggregateTrait
{
/**
* @var \Zend\Stdlib\CallbackHandler[]
* @var callable[]
*/
protected $listeners = array();
protected $listeners = [];
/**
* {@inheritDoc}
@@ -26,9 +26,8 @@ trait ListenerAggregateTrait
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $callback) {
if ($events->detach($callback)) {
unset($this->listeners[$index]);
}
$events->detach($callback);
unset($this->listeners[$index]);
}
}
}

View File

@@ -2,9 +2,9 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
@@ -32,12 +32,10 @@ class ResponseCollection extends SplStack
* Mark the collection as stopped (or its opposite)
*
* @param bool $flag
* @return ResponseCollection
*/
public function setStopped($flag)
{
$this->stopped = (bool) $flag;
return $this;
}
/**

View File

@@ -2,16 +2,13 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Zend\Stdlib\CallbackHandler;
use Zend\Stdlib\PriorityQueue;
/**
* Shared/contextual EventManager
*
@@ -19,158 +16,220 @@ use Zend\Stdlib\PriorityQueue;
* The assumption is that the SharedEventManager will be injected into EventManager
* instances, and then queried for additional listeners when triggering an event.
*/
class SharedEventManager implements
SharedEventAggregateAwareInterface,
SharedEventManagerInterface
class SharedEventManager implements SharedEventManagerInterface
{
/**
* Identifiers with event connections
* @var array
*/
protected $identifiers = array();
protected $identifiers = [];
/**
* Attach a listener to an event
* Attach a listener to an event emitted by components with specific identifiers.
*
* Allows attaching a callback to an event offered by one or more
* identifying components. As an example, the following connects to the
* "getAll" event of both an AbstractResource and EntityResource:
* Allows attaching a listener to an event offered by an identifying
* components. As an example, the following connects to the "getAll" event
* of both an AbstractResource and EntityResource:
*
* <code>
* $sharedEventManager = new SharedEventManager();
* $sharedEventManager->attach(
* array('My\Resource\AbstractResource', 'My\Resource\EntityResource'),
* 'getAll',
* function ($e) use ($cache) {
* if (!$id = $e->getParam('id', false)) {
* return;
* foreach (['My\Resource\AbstractResource', 'My\Resource\EntityResource'] as $identifier) {
* $sharedEventManager->attach(
* $identifier,
* 'getAll',
* function ($e) use ($cache) {
* if (!$id = $e->getParam('id', false)) {
* return;
* }
* if (!$data = $cache->load(get_class($resource) . '::getOne::' . $id )) {
* return;
* }
* return $data;
* }
* if (!$data = $cache->load(get_class($resource) . '::getOne::' . $id )) {
* return;
* }
* return $data;
* }
* );
* );
* }
* </code>
*
* @param string|array $id Identifier(s) for event emitting component(s)
* @param string $identifier Identifier for event emitting component.
* @param string $event
* @param callable $callback PHP Callback
* @param callable $listener Listener that will handle the event.
* @param int $priority Priority at which listener should execute
* @return CallbackHandler|array Either CallbackHandler or array of CallbackHandlers
* @return void
* @throws Exception\InvalidArgumentException for invalid identifier arguments.
* @throws Exception\InvalidArgumentException for invalid event arguments.
*/
public function attach($id, $event, $callback, $priority = 1)
public function attach($identifier, $event, callable $listener, $priority = 1)
{
$ids = (array) $id;
$listeners = array();
foreach ($ids as $id) {
if (!array_key_exists($id, $this->identifiers)) {
$this->identifiers[$id] = new EventManager($id);
if (! is_string($identifier) || empty($identifier)) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid identifier provided; must be a string; received "%s"',
(is_object($identifier) ? get_class($identifier) : gettype($identifier))
));
}
if (! is_string($event) || empty($event)) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid event provided; must be a non-empty string; received "%s"',
(is_object($event) ? get_class($event) : gettype($event))
));
}
$this->identifiers[$identifier][$event][(int) $priority][] = $listener;
}
/**
* @inheritDoc
*/
public function detach(callable $listener, $identifier = null, $eventName = null, $force = false)
{
// No identifier or wildcard identifier: loop through all identifiers and detach
if (null === $identifier || ('*' === $identifier && ! $force)) {
foreach (array_keys($this->identifiers) as $identifier) {
$this->detach($listener, $identifier, $eventName, true);
}
$listeners[] = $this->identifiers[$id]->attach($event, $callback, $priority);
return;
}
if (count($listeners) > 1) {
return $listeners;
if (! is_string($identifier) || empty($identifier)) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid identifier provided; must be a string, received %s',
(is_object($identifier) ? get_class($identifier) : gettype($identifier))
));
}
return $listeners[0];
}
/**
* Attach a listener aggregate
*
* Listener aggregates accept an EventManagerInterface instance, and call attachShared()
* one or more times, typically to attach to multiple events using local
* methods.
*
* @param SharedListenerAggregateInterface $aggregate
* @param int $priority If provided, a suggested priority for the aggregate to use
* @return mixed return value of {@link ListenerAggregateInterface::attachShared()}
*/
public function attachAggregate(SharedListenerAggregateInterface $aggregate, $priority = 1)
{
return $aggregate->attachShared($this, $priority);
}
/**
* Detach a listener from an event offered by a given resource
*
* @param string|int $id
* @param CallbackHandler $listener
* @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
*/
public function detach($id, CallbackHandler $listener)
{
if (!array_key_exists($id, $this->identifiers)) {
return false;
// Do we have any listeners on the provided identifier?
if (! isset($this->identifiers[$identifier])) {
return;
}
return $this->identifiers[$id]->detach($listener);
}
/**
* Detach a listener aggregate
*
* Listener aggregates accept a SharedEventManagerInterface instance, and call detachShared()
* of all previously attached listeners.
*
* @param SharedListenerAggregateInterface $aggregate
* @return mixed return value of {@link SharedListenerAggregateInterface::detachShared()}
*/
public function detachAggregate(SharedListenerAggregateInterface $aggregate)
{
return $aggregate->detachShared($this);
}
/**
* Retrieve all registered events for a given resource
*
* @param string|int $id
* @return array
*/
public function getEvents($id)
{
if (!array_key_exists($id, $this->identifiers)) {
//Check if there are any id wildcards listeners
if ('*' != $id && array_key_exists('*', $this->identifiers)) {
return $this->identifiers['*']->getEvents();
if (null === $eventName || ('*' === $eventName && ! $force)) {
foreach (array_keys($this->identifiers[$identifier]) as $eventName) {
$this->detach($listener, $identifier, $eventName, true);
}
return false;
return;
}
if (! is_string($eventName) || empty($eventName)) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid event name provided; must be a string, received %s',
(is_object($eventName) ? get_class($eventName) : gettype($eventName))
));
}
if (! isset($this->identifiers[$identifier][$eventName])) {
return;
}
foreach ($this->identifiers[$identifier][$eventName] as $priority => $listeners) {
foreach ($listeners as $index => $evaluatedListener) {
if ($evaluatedListener !== $listener) {
continue;
}
// Found the listener; remove it.
unset($this->identifiers[$identifier][$eventName][$priority][$index]);
// Is the priority queue empty?
if (empty($this->identifiers[$identifier][$eventName][$priority])) {
unset($this->identifiers[$identifier][$eventName][$priority]);
break;
}
}
// Is the event queue empty?
if (empty($this->identifiers[$identifier][$eventName])) {
unset($this->identifiers[$identifier][$eventName]);
break;
}
}
// Is the identifier queue now empty? Remove it.
if (empty($this->identifiers[$identifier])) {
unset($this->identifiers[$identifier]);
}
return $this->identifiers[$id]->getEvents();
}
/**
* Retrieve all listeners for a given identifier and event
*
* @param string|int $id
* @param string|int $event
* @return false|PriorityQueue
* @param string[] $identifiers
* @param string $eventName
* @return array[]
* @throws Exception\InvalidArgumentException
*/
public function getListeners($id, $event)
public function getListeners(array $identifiers, $eventName)
{
if (!array_key_exists($id, $this->identifiers)) {
return false;
if ('*' === $eventName || ! is_string($eventName) || empty($eventName)) {
throw new Exception\InvalidArgumentException(sprintf(
'Event name passed to %s must be a non-empty, non-wildcard string',
__METHOD__
));
}
return $this->identifiers[$id]->getListeners($event);
$returnListeners = [];
foreach ($identifiers as $identifier) {
if ('*' === $identifier || ! is_string($identifier) || empty($identifier)) {
throw new Exception\InvalidArgumentException(sprintf(
'Identifier names passed to %s must be non-empty, non-wildcard strings',
__METHOD__
));
}
if (isset($this->identifiers[$identifier])) {
$listenersByIdentifier = $this->identifiers[$identifier];
if (isset($listenersByIdentifier[$eventName])) {
foreach ($listenersByIdentifier[$eventName] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
if (isset($listenersByIdentifier['*'])) {
foreach ($listenersByIdentifier['*'] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
}
}
if (isset($this->identifiers['*'])) {
$wildcardIdentifier = $this->identifiers['*'];
if (isset($wildcardIdentifier[$eventName])) {
foreach ($wildcardIdentifier[$eventName] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
if (isset($wildcardIdentifier['*'])) {
foreach ($wildcardIdentifier['*'] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
}
foreach ($returnListeners as $priority => $listOfListeners) {
$returnListeners[$priority] = array_merge(...$listOfListeners);
}
return $returnListeners;
}
/**
* Clear all listeners for a given identifier, optionally for a specific event
*
* @param string|int $id
* @param null|string $event
* @return bool
* @inheritDoc
*/
public function clearListeners($id, $event = null)
public function clearListeners($identifier, $eventName = null)
{
if (!array_key_exists($id, $this->identifiers)) {
if (! isset($this->identifiers[$identifier])) {
return false;
}
if (null === $event) {
unset($this->identifiers[$id]);
return true;
if (null === $eventName) {
unset($this->identifiers[$identifier]);
return;
}
return $this->identifiers[$id]->clearListeners($event);
if (! isset($this->identifiers[$identifier][$eventName])) {
return;
}
unset($this->identifiers[$identifier][$eventName]);
}
}

View File

@@ -2,64 +2,58 @@
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
use Zend\Stdlib\CallbackHandler;
use Zend\Stdlib\PriorityQueue;
/**
* Interface for shared event listener collections
*/
interface SharedEventManagerInterface
{
/**
* Retrieve all listeners for a given identifier and event
* Attach a listener to an event emitted by components with specific identifiers.
*
* @param string|int $id
* @param string|int $event
* @return false|PriorityQueue
*/
public function getListeners($id, $event);
/**
* Attach a listener to an event
*
* @param string|array $id Identifier(s) for event emitting component(s)
* @param string $event
* @param callable $callback PHP Callback
* @param string $identifier Identifier for event emitting component
* @param string $eventName
* @param callable $listener Listener that will handle the event.
* @param int $priority Priority at which listener should execute
* @return CallbackHandler|array Either CallbackHandler or array of CallbackHandlers
*/
public function attach($id, $event, $callback, $priority = 1);
public function attach($identifier, $eventName, callable $listener, $priority = 1);
/**
* Detach a listener from an event offered by a given resource
* Detach a shared listener.
*
* @param string|int $id
* @param CallbackHandler $listener
* @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
* Allows detaching a listener from one or more events to which it may be
* attached.
*
* @param callable $listener Listener to detach.
* @param null|string $identifier Identifier from which to detach; null indicates
* all registered identifiers.
* @param null|string $eventName Event from which to detach; null indicates
* all registered events.
* @throws Exception\InvalidArgumentException for invalid identifier arguments.
* @throws Exception\InvalidArgumentException for invalid event arguments.
*/
public function detach($id, CallbackHandler $listener);
public function detach(callable $listener, $identifier = null, $eventName = null);
/**
* Retrieve all registered events for a given resource
* Retrieve all listeners for given identifiers
*
* @param string|int $id
* @param array $identifiers
* @param string $eventName
* @return array
*/
public function getEvents($id);
public function getListeners(array $identifiers, $eventName);
/**
* Clear all listeners for a given identifier, optionally for a specific event
*
* @param string|int $id
* @param null|string $event
* @return bool
* @param string $identifier
* @param null|string $eventName
*/
public function clearListeners($id, $event = null);
public function clearListeners($identifier, $eventName = null);
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager;
/**
* Interface indicating that an object composes or can compose a
* SharedEventManagerInterface instance.
*/
interface SharedEventsCapableInterface
{
/**
* Retrieve the shared event manager, if composed.
*
* @return null|SharedEventManagerInterface
*/
public function getSharedManager();
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/
namespace Zend\EventManager\Test;
use PHPUnit\Framework\Assert;
use ReflectionProperty;
use Zend\EventManager\EventManager;
/**
* Trait providing utility methods and assertions for use in PHPUnit test cases.
*
* This trait may be composed into a test case, and provides:
*
* - methods for introspecting events and listeners
* - methods for asserting listeners are attached at a specific priority
*
* Some functionality in this trait duplicates functionality present in the
* version 2 EventManagerInterface and/or EventManager implementation, but
* abstracts that functionality for use in v3. As such, components or code
* that is testing for listener registration should use the methods in this
* trait to ensure tests are forwards-compatible between zend-eventmanager
* versions.
*/
trait EventListenerIntrospectionTrait
{
/**
* Retrieve a list of event names from an event manager.
*
* @param EventManager $events
* @return string[]
*/
private function getEventsFromEventManager(EventManager $events)
{
$r = new ReflectionProperty($events, 'events');
$r->setAccessible(true);
$listeners = $r->getValue($events);
return array_keys($listeners);
}
/**
* Retrieve an interable list of listeners for an event.
*
* Given an event and an event manager, returns an iterator with the
* listeners for that event, in priority order.
*
* If $withPriority is true, the key values will be the priority at which
* the given listener is attached.
*
* Do not pass $withPriority if you want to cast the iterator to an array,
* as many listeners will likely have the same priority, and thus casting
* will collapse to the last added.
*
* @param string $event
* @param EventManager $events
* @param bool $withPriority
* @return \Traversable
*/
private function getListenersForEvent($event, EventManager $events, $withPriority = false)
{
$r = new ReflectionProperty($events, 'events');
$r->setAccessible(true);
$internal = $r->getValue($events);
$listeners = [];
foreach (isset($internal[$event]) ? $internal[$event] : [] as $p => $listOfListeners) {
foreach ($listOfListeners as $l) {
$listeners[$p] = isset($listeners[$p]) ? array_merge($listeners[$p], $l) : $l;
}
}
return $this->traverseListeners($listeners, $withPriority);
}
/**
* Assert that a given listener exists at the specified priority.
*
* @param callable $expectedListener
* @param int $expectedPriority
* @param string $event
* @param EventManager $events
* @param string $message Failure message to use, if any.
*/
private function assertListenerAtPriority(
callable $expectedListener,
$expectedPriority,
$event,
EventManager $events,
$message = ''
) {
$message = $message ?: sprintf(
'Listener not found for event "%s" and priority %d',
$event,
$expectedPriority
);
$listeners = $this->getListenersForEvent($event, $events, true);
$found = false;
foreach ($listeners as $priority => $listener) {
if ($listener === $expectedListener
&& $priority === $expectedPriority
) {
$found = true;
break;
}
}
Assert::assertTrue($found, $message);
}
/**
* Returns an indexed array of listeners for an event.
*
* Returns an indexed array of listeners for an event, in priority order.
* Priority values will not be included; use this only for testing if
* specific listeners are present, or for a count of listeners.
*
* @param string $event
* @param EventManager $events
* @return callable[]
*/
private function getArrayOfListenersForEvent($event, EventManager $events)
{
return iterator_to_array($this->getListenersForEvent($event, $events));
}
/**
* Generator for traversing listeners in priority order.
*
* @param array $listeners
* @param bool $withPriority When true, yields priority as key.
*/
public function traverseListeners(array $queue, $withPriority = false)
{
krsort($queue, SORT_NUMERIC);
foreach ($queue as $priority => $listeners) {
$priority = (int) $priority;
foreach ($listeners as $listener) {
if ($withPriority) {
yield $priority => $listener;
} else {
yield $listener;
}
}
}
}
}