CHANGELOG.md000064400000025064144760110070006364 0ustar00# Changelog All notable changes to this project will be documented in this file, in reverse chronological order by release. ## 3.3.0 - 2020-08-25 ### Added - [#10](https://github.com/laminas/laminas-eventmanager/pull/10) adds support for the upcoming PHP 8.0 release. - [zendframework/zend-eventmanager#72](https://github.com/zendframework/zend-eventmanager/pull/72) adds support for PHP 7.3. ### Changed - Nothing. ### Deprecated - Nothing. ### Removed - [#10](https://github.com/laminas/laminas-eventmanager/pull/10) removes support for PHP versions prior to PHP 7.3. ### Fixed - Nothing. ## 3.2.1 - 2018-04-25 ### Added - [zendframework/zend-eventmanager#66](https://github.com/zendframework/zend-eventmanager/pull/66) adds support for PHP 7.2. ### Changed - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - Nothing. ## 3.2.0 - 2017-07-11 ### Added - Nothing. ### Deprecated - Nothing. ### Removed - [zendframework/zend-eventmanager#47](https://github.com/zendframework/zend-eventmanager/pull/47) removes support for PHP 5.5 and HHVM. ### Fixed - Nothing. ## 3.1.0 - 2016-12-19 ### Added - [zendframework/zend-eventmanager#26](https://github.com/zendframework/zend-eventmanager/pull/26) publishes the documentation to https://docs.laminas.dev/laminas-eventmanager/ ### Changes - [zendframework/zend-eventmanager#17](https://github.com/zendframework/zend-eventmanager/pull/17) makes a number of internal changes to how listeners are stored in order to improve performance, by as much as 10% in the scenario used in the MVC layer. Additionally, it optimizes when the target and event arguments are injected into an event, eliminating that step entirely when either is unavailable. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - Nothing. ## 3.0.1 - 2016-02-18 ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [zendframework/zend-eventmanager#24](https://github.com/zendframework/zend-eventmanager/pull/24) updates the laminas-stdlib dependency to `^2.7.3 || ^3.0`, allowing either major version. ## 3.0.0 - 2016-01-12 ### Added - [Migration documentation](doc/book/migration/) was added. - [Automated benchmarks](benchmarks/) were added. - `EventManager::__construct()` now accepts an optional `SharedEventManagerInterface` instance as the first argument, and an optional array of identifiers as the second. As identifiers have no meaning without a shared manager present, they are secondary to providing the shared manager. - `EventManagerInterface::trigger()` changes its signature to `trigger($eventName, $target = null, $argv = [])`; each argument has exactly one possible meaning; the `$eventName` can only be a string event name. The fourth `$callback` argument is removed. - `EventManagerInterface::triggerUntil()` changes its signature to `triggerUntil(callable $callback, $eventName, $target = null, $argv = null)`. Each argument has exactly one meaning. - `EventManagerInterface` adds two new methods for triggering provided `EventInterface` arguments: `triggerEvent(EventInterface $event)` and `triggerEventUntil(callable $callback, EventInterface $event)`. - `EventManagerInterface::attach()` and `detach()` change their signatures to `attach($eventName, callable $listener, $priority = 1)` and `detach(callable $listener, $eventName = null)`, respectively. Note that `$eventName` can now only be a string event name, not an array or `Traversable`. - `EventManagerInterface::setIdentifiers()` and `addIdentifiers()` change their signatures to each only accept an *array* of identifiers. - `SharedEventManagerInterface::getListeners()` changes signature to `getListeners(array $identifiers, $eventName)` and now guarantees return of an array. Note that the second argument is now *required*. - `SharedEventManagerInterface::attach()` changes signature to `attach($identifier, $eventName, callable $listener, $priority = 1)`. The `$identifier` and `$eventName` **must** be strings. - `SharedEventManagerInterface::detach()` changes signature to `detach(callable $listener, $identifier = null, $eventName = null)`; `$identifier` and `$eventName` **must** be strings if passed. - `ListenerAggregateInterface::attach()` adds an optional `$priority = 1` argument. This was used already in v2, but not dictated by the interface. - `FilterInterface::attach()` and `detach()` have changed signature to `attach(callable $callback)` and `detach(callable $ilter)`, respectively. - `LazyListener` allows wrapping: - fetching a listener service from a container-interop container, and - invoking a designated listener method with the provided event. - `LazyEventListener` extends `LazyListener`, and provides metadata for discovering the intended event name and priority at which to attach the lazy listener; these are consumed by: - `LazyListenerAggregate`, which, provided a list of `LazyEventListeners` and/or definitions to use to create them, acts as an aggregate for attaching a number of such listeners at once. - [zendframework/zend-eventmanager#20](https://github.com/zendframework/zend-eventmanager/pull/20) updates the trait `Laminas\EventManager\Test\EventListenerIntrospectionTrait` so that the implementation will work with the v3 changes; the tests written for v2 continue to pass, allowing this trait to be used to provide compatibility testing between v2 and v3. ### Deprecated - Nothing. ### Removed - `GlobalEventManager` and `StaticEventManager` are removed (with prejudice!). - `ProvidesEvents`, which was previously deprecated, is removed. - `EventManagerInterface::setSharedManager()` is removed. Shared managers are now expected to be injected during instantiation. - `EventManagerInterface::getEvents()` and `getListeners()` are removed; they had now purpose within the implementation. - `EventManagerInterface::setEventClass()` was renamed to `setEventPrototype()`, which now expects an `EventInterface` instance. That instance will be cloned whenever a new event is created. - `EventManagerInterface::attachAggregate()` and `detachAggregate()` are removed. Users should use the `attach()` and `detach()` methods of the aggregates themselves. - `SharedEventAggregateAwareInterface` and `SharedListenerAggregateInterface` are removed. This was an undocumented and largely unused feature. - `SharedEventManagerAwareInterface` is removed. A new interface, `SharedEventsCapableInterface` defines the `getSharedManager()` method from the interface, and `EventManagerInterface` extends that new interface. - `SharedEventManagerInterface::getEvents()` is removed, as it had no purpose in the implementation. - `ResponseCollection::setStopped()` no longer implements a fluent interface. ### Fixed - `FilterIterator::insert()` has been modified to raise an exception if the value provided is not a callable. ## 2.6.2 - 2016-01-12 ### Added - [zendframework/zend-eventmanager#19](https://github.com/zendframework/zend-eventmanager/pull/19) adds a new trait, `Laminas\EventManager\Test\EventListenerIntrospectionTrait`, intended for composition in unit tests. It provides a number of methods that can be used to retrieve listeners with or without associated priority, and the assertion `assertListenerAtPriority(callable $listener, $priority, $event, EventManager $events, $message = '')`, which can be used for testing that a listener was registered at the specified priority with the specified event. The features in this patch are intended to facilitate testing against both version 2 and version 3 of laminas-eventmanager, as it provides a consistent API for retrieving lists of events and listeners between the two versions. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - Nothing. ## 2.6.0 - 2015-09-29 ### Added - Added `Laminas\EventManager\SharedEventsCapableInterface`. This interface will largely replace `Laminas\EventManager\SharedEventManagerAwareInterface` in version 3, and the latter was updated to extend it. - Added `EventManager::triggerEvent(EventInterface $event)` as a forwards-compatibility feature. - Add `EventManager::triggerEventUntil(callable $callback, EventIterface $event)` as a forwards-compatibility feature. - Adds [Athletic](https://github.com/polyfractal/athletic) benchmarks to aid in gauging performanc impact of changes; these are a development change only. ### Deprecated - Marked `GlobalEventManager` as deprecated; this class will be removed in version 3. - Marked `StaticEventManager` as deprecated; this class will be removed in version 3. - Marked `SharedListenerAggregateInterface` as deprecated; this interface will be removed in version 3. - Marked `SharedEventAggregateAwareInterface` as deprecated; this interface will be removed in version 3. - Marked `SharedEventManagerAwareInterface` as deprecated; this interface will be removed in version 3. - Marked `EventManager::setSharedManager()` as deprecated; this method will be removed in version 3. - Marked `EventManager::unsetSharedManager()` as deprecated; this method will be removed in version 3. - Marked `EventManagerInterface::` and `EventManager::getEvents()` as deprecated; this method will be removed in version 3. - Marked `EventManagerInterface::` and `EventManager::getListeners()` as deprecated; this method will be removed in version 3. - Marked `EventManagerInterface::` and `Eventmanager::setEventClass()` as deprecated; this method is renamed to `setEventPrototype(EventInterface $event)` in version 3. - Marked `EventManagerInterface::` and `EventManager::attachAggregate()` as deprecated; this method will be removed in version 3. - Marked `EventManagerInterface::` and `EventManager::detachAggregate()` as deprecated; this method will be removed in version 3. - Marked `SharedEventManagerInterface::` and `SharedEventManager::getEvents()` as deprecated; this method will be removed in version 3. ### Removed - Nothing. ### Fixed - Nothing. ## 2.5.2 - 2015-07-16 ### Added - [zendframework/zend-eventmanager#5](https://github.com/zendframework/zend-eventmanager/pull/5) adds a number of unit tests to improve test coverage, and thus maintainability and stability. ### Deprecated - Nothing. ### Removed - [zendframework/zend-eventmanager#3](https://github.com/zendframework/zend-eventmanager/pull/3) removes some PHP 5.3- and 5.4-isms (such as marking Traits as requiring 5.4, and closing over a copy of `$this`) from the test suite. ### Fixed - [zendframework/zend-eventmanager#5](https://github.com/zendframework/zend-eventmanager/pull/5) fixes a bug in `FilterIterator` that occurs when attempting to extract from an empty heap. COPYRIGHT.md000064400000000133144760110070006433 0ustar00Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) LICENSE.md000064400000002732144760110070006154 0ustar00Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Laminas Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. README.md000064400000002471144760110070006027 0ustar00# laminas-eventmanager [![Build Status](https://travis-ci.com/laminas/laminas-eventmanager.svg?branch=master)](https://travis-ci.com/laminas/laminas-eventmanager) [![Coverage Status](https://coveralls.io/repos/laminas/laminas-eventmanager/badge.svg?branch=master)](https://coveralls.io/r/laminas/laminas-eventmanager?branch=master) laminas-eventmanager is designed for the following use cases: - Implementing simple subject/observer patterns. - Implementing Aspect-Oriented designs. - Implementing event-driven architectures. The basic architecture allows you to attach and detach listeners to named events, both on a per-instance basis as well as via shared collections; trigger events; and interrupt execution of listeners. - File issues at https://github.com/laminas/laminas-eventmanager/issues - Documentation is at https://docs.laminas.dev/laminas-eventmanager/ For migration from version 2 to version 3, please [read the migration documentation](https://docs.laminas.dev/laminas-eventmanager/migration/intro/). ## Benchmarks We provide scripts for benchmarking laminas-eventmanager using the [PHPBench](https://github.com/phpbench/phpbench) framework; these can be found in the `benchmarks/` directory. To execute the benchmarks you can run the following command: ```bash $ vendor/bin/phpbench run --report=aggregate ``` composer.json000064400000004145144760110070007272 0ustar00{ "name": "laminas/laminas-eventmanager", "description": "Trigger and listen to events within a PHP application", "license": "BSD-3-Clause", "keywords": [ "laminas", "event", "events", "eventmanager" ], "homepage": "https://laminas.dev", "support": { "docs": "https://docs.laminas.dev/laminas-eventmanager/", "issues": "https://github.com/laminas/laminas-eventmanager/issues", "source": "https://github.com/laminas/laminas-eventmanager", "rss": "https://github.com/laminas/laminas-eventmanager/releases.atom", "chat": "https://laminas.dev/chat", "forum": "https://discourse.laminas.dev" }, "config": { "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "3.3.x-dev", "dev-develop": "3.4.x-dev" } }, "require": { "php": "^7.3 || ^8.0", "laminas/laminas-zendframework-bridge": "^1.0" }, "require-dev": { "container-interop/container-interop": "^1.1", "laminas/laminas-coding-standard": "~1.0.0", "laminas/laminas-stdlib": "^2.7.3 || ^3.0", "phpbench/phpbench": "^0.17.1", "phpunit/phpunit": "^8.5.8" }, "suggest": { "container-interop/container-interop": "^1.1, to use the lazy listeners feature", "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" }, "autoload": { "psr-4": { "Laminas\\EventManager\\": "src/" } }, "autoload-dev": { "psr-4": { "LaminasTest\\EventManager\\": "test/", "LaminasBench\\EventManager\\": "benchmarks/" }, "files": [ "test/_autoload.php" ] }, "scripts": { "check": [ "@cs-check", "@test" ], "cs-check": "phpcs", "cs-fix": "phpcbf", "test": "phpunit --colors=always", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" }, "replace": { "zendframework/zend-eventmanager": "^3.2.1" } } src/AbstractListenerAggregate.php000064400000001426144760110070013127 0ustar00listeners as $index => $callback) { $events->detach($callback); unset($this->listeners[$index]); } } } src/Event.php000064400000010652144760110070007131 0ustar00setName($name); } if (null !== $target) { $this->setTarget($target); } if (null !== $params) { $this->setParams($params); } } /** * Get event name * * @return string */ public function getName() { return $this->name; } /** * Get the event target * * This may be either an object, or the name of a static method. * * @return string|object */ public function getTarget() { return $this->target; } /** * Set parameters * * Overwrites parameters * * @param array|ArrayAccess|object $params * @throws Exception\InvalidArgumentException */ public function setParams($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; } /** * Get all parameters * * @return array|object|ArrayAccess */ public function getParams() { return $this->params; } /** * Get an individual parameter * * If the parameter does not exist, the $default value will be returned. * * @param string|int $name * @param mixed $default * @return mixed */ public function getParam($name, $default = null) { // Check in params that are arrays or implement array access if (is_array($this->params) || $this->params instanceof ArrayAccess) { if (! isset($this->params[$name])) { return $default; } return $this->params[$name]; } // Check in normal objects if (! isset($this->params->{$name})) { return $default; } return $this->params->{$name}; } /** * Set the event name * * @param string $name */ public function setName($name) { $this->name = (string) $name; } /** * Set the event target/context * * @param null|string|object $target */ public function setTarget($target) { $this->target = $target; } /** * Set an individual parameter to a value * * @param string|int $name * @param mixed $value */ public function setParam($name, $value) { if (is_array($this->params) || $this->params instanceof ArrayAccess) { // Arrays or objects implementing array access $this->params[$name] = $value; return; } // Objects $this->params->{$name} = $value; } /** * Stop further event propagation * * @param bool $flag */ public function stopPropagation($flag = true) { $this->stopPropagation = (bool) $flag; } /** * Is propagation stopped? * * @return bool */ public function propagationIsStopped() { return $this->stopPropagation; } } src/EventInterface.php000064400000003773144760110070010760 0ustar00 => [ * => [ * 0 => [, ...] * ], * ... * ], * ... * ] * * 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; } } src/EventManagerAwareInterface.php000064400000001265144760110070013225 0ustar00eventIdentifier property. * * @param EventManagerInterface $events */ public function setEventManager(EventManagerInterface $events) { $identifiers = [__CLASS__, get_class($this)]; if (isset($this->eventIdentifier)) { if ((is_string($this->eventIdentifier)) || (is_array($this->eventIdentifier)) || ($this->eventIdentifier instanceof Traversable) ) { $identifiers = array_unique(array_merge($identifiers, (array) $this->eventIdentifier)); } elseif (is_object($this->eventIdentifier)) { $identifiers[] = $this->eventIdentifier; } // silently ignore invalid eventIdentifier types } $events->setIdentifiers($identifiers); $this->events = $events; if (method_exists($this, 'attachDefaultListeners')) { $this->attachDefaultListeners(); } } /** * Retrieve the event manager * * Lazy-loads an EventManager instance if none registered. * * @return EventManagerInterface */ public function getEventManager() { if (! $this->events instanceof EventManagerInterface) { $this->setEventManager(new EventManager()); } return $this->events; } } src/EventManagerInterface.php000064400000012470144760110070012245 0ustar00setExtractFlags(self::EXTR_BOTH); // Iterate and remove any matches $removed = false; $items = []; $this->rewind(); while (! $this->isEmpty()) { $item = $this->extract(); if ($item['data'] === $datum) { $removed = true; continue; } $items[] = $item; } // Repopulate foreach ($items as $item) { $this->insert($item['data'], $item['priority']); } $this->setExtractFlags(self::EXTR_DATA); return $removed; } /** * Iterate the next filter in the chain * * Iterates and calls the next filter in the chain. * * @param mixed $context * @param array $params * @param FilterIterator $chain * @return mixed */ public function next($context = null, array $params = [], $chain = null) { if (empty($context) || ($chain instanceof FilterIterator && $chain->isEmpty())) { return; } //We can't extract from an empty heap if ($this->isEmpty()) { return; } $next = $this->extract(); return $next($context, $params, $chain); } } src/FilterChain.php000064400000005120144760110070010232 0ustar00filters = new Filter\FilterIterator(); } /** * Apply the filters * * Begins iteration of the filters. * * @param mixed $context Object under observation * @param mixed $argv Associative array of arguments * @return mixed */ public function run($context, array $argv = []) { $chain = clone $this->getFilters(); if ($chain->isEmpty()) { return; } $next = $chain->extract(); 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) * @return CallbackHandler (to allow later unsubscribe) * @throws Exception\InvalidCallbackException */ public function attach(callable $callback, $priority = 1) { $this->filters->insert($callback, $priority); return $callback; } /** * Detach a filter from the chain * * @param callable $filter * @return bool Returns true if filter found and unsubscribed; returns false otherwise */ public function detach(callable $filter) { return $this->filters->remove($filter); } /** * Retrieve all filters * * @return Filter\FilterIterator */ public function getFilters() { return $this->filters; } /** * Clear all filters * * @return void */ public function clearFilters() { $this->filters = new Filter\FilterIterator(); } /** * Return current responses * * Only available while the chain is still being iterated. Returns the * current ResponseCollection. * * @return null|ResponseCollection */ public function getResponses() { return; } } src/LazyEventListener.php000064400000004072144760110070011476 0ustar00event = $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; } } src/LazyListener.php000064400000007025144760110070010475 0ustar00service = $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 Laminas\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; } } src/LazyListenerAggregate.php000064400000007125144760110070012305 0ustar00 * $aggregate = new LazyListenerAggregate( * $lazyEventListenersOrDefinitions, * $container * ); * $aggregate->attach($events); * */ 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) ); } } } src/ListenerAggregateInterface.php000064400000002407144760110070013264 0ustar00listeners as $index => $callback) { $events->detach($callback); unset($this->listeners[$index]); } } } src/ResponseCollection.php000064400000003476144760110070011670 0ustar00stopped; } /** * Mark the collection as stopped (or its opposite) * * @param bool $flag */ public function setStopped($flag) { $this->stopped = (bool) $flag; } /** * Convenient access to the first handler return value. * * @return mixed The first handler return value */ public function first() { return parent::bottom(); } /** * Convenient access to the last handler return value. * * If the collection is empty, returns null. Otherwise, returns value * returned by last handler. * * @return mixed The last handler return value */ public function last() { if (count($this) === 0) { return; } return parent::top(); } /** * Check if any of the responses match the given value. * * @param mixed $value The value to look for among responses * @return bool */ public function contains($value) { foreach ($this as $response) { if ($response === $value) { return true; } } return false; } } src/SharedEventManager.php000064400000021100144760110070011541 0ustar00 * $sharedEventManager = new SharedEventManager(); * 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; * } * ); * } * * * @param string $identifier Identifier for event emitting component. * @param string $event * @param callable $listener Listener that will handle the event. * @param int $priority Priority at which listener should execute * @return void * @throws Exception\InvalidArgumentException for invalid identifier arguments. * @throws Exception\InvalidArgumentException for invalid event arguments. */ public function attach($identifier, $event, callable $listener, $priority = 1) { 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); } return; } 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)) )); } // Do we have any listeners on the provided identifier? if (! isset($this->identifiers[$identifier])) { return; } if (null === $eventName || ('*' === $eventName && ! $force)) { foreach (array_keys($this->identifiers[$identifier]) as $eventName) { $this->detach($listener, $identifier, $eventName, true); } 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]); } } /** * Retrieve all listeners for a given identifier and event * * @param string[] $identifiers * @param string $eventName * @return array[] * @throws Exception\InvalidArgumentException */ public function getListeners(array $identifiers, $eventName) { 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__ )); } $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; } /** * @inheritDoc */ public function clearListeners($identifier, $eventName = null) { if (! isset($this->identifiers[$identifier])) { return false; } if (null === $eventName) { unset($this->identifiers[$identifier]); return; } if (! isset($this->identifiers[$identifier][$eventName])) { return; } unset($this->identifiers[$identifier][$eventName]); } } src/SharedEventManagerInterface.php000064400000004021144760110070013365 0ustar00setAccessible(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; } } } } }