File indexing completed on 2024-05-12 06:02:28

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_EventManager
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  */
0020 
0021 // require_once 'Zend/EventManager/Event.php';
0022 // require_once 'Zend/EventManager/EventCollection.php';
0023 // require_once 'Zend/EventManager/ResponseCollection.php';
0024 // require_once 'Zend/EventManager/SharedEventCollectionAware.php';
0025 // require_once 'Zend/EventManager/StaticEventManager.php';
0026 // require_once 'Zend/Stdlib/CallbackHandler.php';
0027 // require_once 'Zend/Stdlib/PriorityQueue.php';
0028 
0029 /**
0030  * Event manager: notification system
0031  *
0032  * Use the EventManager when you want to create a per-instance notification
0033  * system for your objects.
0034  *
0035  * @category   Zend
0036  * @package    Zend_EventManager
0037  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0038  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0039  */
0040 class Zend_EventManager_EventManager implements Zend_EventManager_EventCollection, Zend_EventManager_SharedEventCollectionAware
0041 {
0042     /**
0043      * Subscribed events and their listeners
0044      * @var array Array of Zend_Stdlib_PriorityQueue objects
0045      */
0046     protected $events = array();
0047 
0048     /**
0049      * @var string Class representing the event being emitted
0050      */
0051     protected $eventClass = 'Zend_EventManager_Event';
0052 
0053     /**
0054      * Identifiers, used to pull static signals from StaticEventManager
0055      * @var array
0056      */
0057     protected $identifiers = array();
0058 
0059     /**
0060      * Static collections
0061      * @var false|null|Zend_EventManager_StaticEventCollection
0062      */
0063     protected $sharedCollections = null;
0064 
0065     /**
0066      * Constructor
0067      *
0068      * Allows optionally specifying identifier(s) to use to pull signals from a
0069      * StaticEventManager.
0070      *
0071      * @param  null|string|int|array|Traversable $identifiers
0072      * @return void
0073      */
0074     public function __construct($identifiers = null)
0075     {
0076         $this->setIdentifiers($identifiers);
0077     }
0078 
0079     /**
0080      * Set the event class to utilize
0081      *
0082      * @param  string $class
0083      * @return Zend_EventManager_EventManager
0084      */
0085     public function setEventClass($class)
0086     {
0087         $this->eventClass = $class;
0088         return $this;
0089     }
0090 
0091     /**
0092      * Set static collections container
0093      *
0094      * @param  Zend_EventManager_SharedEventCollection $collections
0095      * @return $this
0096      */
0097     public function setSharedCollections(Zend_EventManager_SharedEventCollection $collections)
0098     {
0099         $this->sharedCollections = $collections;
0100         return $this;
0101     }
0102 
0103     /**
0104      * Remove any shared collections
0105      *
0106      * Sets {@link $sharedCollections} to boolean false to disable ability
0107      * to lazy-load static event manager instance.
0108      * 
0109      * @return void
0110      */
0111     public function unsetSharedCollections()
0112     {
0113         $this->sharedCollections = false;
0114     }
0115 
0116     /**
0117      * Get static collections container
0118      *
0119      * @return false|Zend_EventManager_SharedEventCollection
0120      */
0121     public function getSharedCollections()
0122     {
0123         if (null === $this->sharedCollections) {
0124             $this->setSharedCollections(Zend_EventManager_StaticEventManager::getInstance());
0125         }
0126         return $this->sharedCollections;
0127     }
0128 
0129     /**
0130      * Get the identifier(s) for this Zend_EventManager_EventManager
0131      *
0132      * @return array
0133      */
0134     public function getIdentifiers()
0135     {
0136         return $this->identifiers;
0137     }
0138 
0139     /**
0140      * Set the identifiers (overrides any currently set identifiers)
0141      *
0142      * @param string|int|array|Traversable $identifiers
0143      * @return Zend_EventManager_EventManager
0144      */
0145     public function setIdentifiers($identifiers)
0146     {
0147         if (is_array($identifiers) || $identifiers instanceof Traversable) {
0148             $this->identifiers = array_unique((array) $identifiers);
0149         } elseif ($identifiers !== null) {
0150             $this->identifiers = array($identifiers);
0151         }
0152         return $this;
0153     }
0154 
0155     /**
0156      * Add some identifier(s) (appends to any currently set identifiers)
0157      *
0158      * @param string|int|array|Traversable $identifiers
0159      * @return Zend_EventManager_EventManager
0160      */
0161     public function addIdentifiers($identifiers)
0162     {
0163         if (is_array($identifiers) || $identifiers instanceof Traversable) {
0164             $this->identifiers = array_unique($this->identifiers + (array) $identifiers);
0165         } elseif ($identifiers !== null) {
0166             $this->identifiers = array_unique(array_merge($this->identifiers, array($identifiers)));
0167         }
0168         return $this;
0169     }
0170 
0171     /**
0172      * Trigger all listeners for a given event
0173      *
0174      * Can emulate triggerUntil() if the last argument provided is a callback.
0175      *
0176      * @param  string $event
0177      * @param  string|object $target Object calling emit, or symbol describing target (such as static method name)
0178      * @param  array|ArrayAccess $argv Array of arguments; typically, should be associative
0179      * @param  null|callback $callback
0180      * @return Zend_EventManager_ResponseCollection All listener return values
0181      */
0182     public function trigger($event, $target = null, $argv = array(), $callback = null)
0183     {
0184         if ($event instanceof Zend_EventManager_EventDescription) {
0185             $e        = $event;
0186             $event    = $e->getName();
0187             $callback = $target;
0188         } elseif ($target instanceof Zend_EventManager_EventDescription) {
0189             $e = $target;
0190             $e->setName($event);
0191             $callback = $argv;
0192         } elseif ($argv instanceof Zend_EventManager_EventDescription) {
0193             $e = $argv;
0194             $e->setName($event);
0195             $e->setTarget($target);
0196         } else {
0197             $e = new $this->eventClass();
0198             $e->setName($event);
0199             $e->setTarget($target);
0200             $e->setParams($argv);
0201         }
0202 
0203         if ($callback && !is_callable($callback)) {
0204             // require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
0205             throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided');
0206         }
0207 
0208         return $this->triggerListeners($event, $e, $callback);
0209     }
0210 
0211     /**
0212      * Trigger listeners until return value of one causes a callback to
0213      * evaluate to true
0214      *
0215      * Triggers listeners until the provided callback evaluates the return
0216      * value of one as true, or until all listeners have been executed.
0217      *
0218      * @param  string $event
0219      * @param  string|object $target Object calling emit, or symbol describing target (such as static method name)
0220      * @param  array|ArrayAccess $argv Array of arguments; typically, should be associative
0221      * @param  Callable $callback
0222      * @throws Zend_Stdlib_Exception_InvalidCallbackException if invalid callback provided
0223      */
0224     public function triggerUntil($event, $target, $argv = null, $callback = null)
0225     {
0226         if ($event instanceof Zend_EventManager_EventDescription) {
0227             $e        = $event;
0228             $event    = $e->getName();
0229             $callback = $target;
0230         } elseif ($target instanceof Zend_EventManager_EventDescription) {
0231             $e = $target;
0232             $e->setName($event);
0233             $callback = $argv;
0234         } elseif ($argv instanceof Zend_EventManager_EventDescription) {
0235             $e = $argv;
0236             $e->setName($event);
0237             $e->setTarget($target);
0238         } else {
0239             $e = new $this->eventClass();
0240             $e->setName($event);
0241             $e->setTarget($target);
0242             $e->setParams($argv);
0243         }
0244 
0245         if (!is_callable($callback)) {
0246             // require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
0247             throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided');
0248         }
0249 
0250         return $this->triggerListeners($event, $e, $callback);
0251     }
0252 
0253     /**
0254      * Attach a listener to an event
0255      *
0256      * The first argument is the event, and the next argument describes a
0257      * callback that will respond to that event. A CallbackHandler instance
0258      * describing the event listener combination will be returned.
0259      *
0260      * The last argument indicates a priority at which the event should be
0261      * executed. By default, this value is 1; however, you may set it for any
0262      * integer value. Higher values have higher priority (i.e., execute first).
0263      *
0264      * You can specify "*" for the event name. In such cases, the listener will 
0265      * be triggered for every event.
0266      *
0267      * @param  string|array|Zend_EventManager_ListenerAggregate $event An event or array of event names. If a ListenerAggregate, proxies to {@link attachAggregate()}.
0268      * @param  callback|int $callback If string $event provided, expects PHP callback; for a ListenerAggregate $event, this will be the priority
0269      * @param  int $priority If provided, the priority at which to register the callback
0270      * @return Zend_Stdlib_CallbackHandler|mixed CallbackHandler if attaching callback (to allow later unsubscribe); mixed if attaching aggregate
0271      */
0272     public function attach($event, $callback = null, $priority = 1)
0273     {
0274         // Proxy ListenerAggregate arguments to attachAggregate()
0275         if ($event instanceof Zend_EventManager_ListenerAggregate) {
0276             return $this->attachAggregate($event, $callback);
0277         }
0278 
0279         // Null callback is invalid
0280         if (null === $callback) {
0281             // require_once 'Zend/EventManager/Exception/InvalidArgumentException.php';
0282             throw new Zend_EventManager_Exception_InvalidArgumentException(sprintf(
0283                 '%s: expects a callback; none provided',
0284                 __METHOD__
0285             ));
0286         }
0287 
0288         // Array of events should be registered individually, and return an array of all listeners
0289         if (is_array($event)) {
0290             $listeners = array();
0291             foreach ($event as $name) {
0292                 $listeners[] = $this->attach($name, $callback, $priority);
0293             }
0294             return $listeners;
0295         }
0296 
0297         // If we don't have a priority queue for the event yet, create one
0298         if (empty($this->events[$event])) {
0299             $this->events[$event] = new Zend_Stdlib_PriorityQueue();
0300         }
0301 
0302         // Create a callback handler, setting the event and priority in its metadata
0303         $listener = new Zend_Stdlib_CallbackHandler($callback, array('event' => $event, 'priority' => $priority));
0304 
0305         // Inject the callback handler into the queue
0306         $this->events[$event]->insert($listener, $priority);
0307         return $listener;
0308     }
0309 
0310     /**
0311      * Attach a listener aggregate
0312      *
0313      * Listener aggregates accept an EventCollection instance, and call attach()
0314      * one or more times, typically to attach to multiple events using local
0315      * methods.
0316      *
0317      * @param  Zend_EventManager_ListenerAggregate $aggregate
0318      * @param  int $priority If provided, a suggested priority for the aggregate to use
0319      * @return mixed return value of {@link Zend_EventManager_ListenerAggregate::attach()}
0320      */
0321     public function attachAggregate(Zend_EventManager_ListenerAggregate $aggregate, $priority = 1)
0322     {
0323         return $aggregate->attach($this, $priority);
0324     }
0325 
0326     /**
0327      * Unsubscribe a listener from an event
0328      *
0329      * @param  Zend_Stdlib_CallbackHandler|Zend_EventManager_ListenerAggregate $listener
0330      * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
0331      * @throws Zend_EventManager_Exception_InvalidArgumentException if invalid listener provided
0332      */
0333     public function detach($listener)
0334     {
0335         if ($listener instanceof Zend_EventManager_ListenerAggregate) {
0336             return $this->detachAggregate($listener);
0337         }
0338 
0339         if (!$listener instanceof Zend_Stdlib_CallbackHandler) {
0340             // require_once 'Zend/EventManager/Exception/InvalidArgumentException.php';
0341             throw new Zend_EventManager_Exception_InvalidArgumentException(sprintf(
0342                 '%s: expected a Zend_EventManager_ListenerAggregate or Zend_Stdlib_CallbackHandler; received "%s"',
0343                 __METHOD__,
0344                 (is_object($listener) ? get_class($listener) : gettype($listener))
0345             ));
0346         }
0347 
0348         $event = $listener->getMetadatum('event');
0349         if (!$event || empty($this->events[$event])) {
0350             return false;
0351         }
0352         $return = $this->events[$event]->remove($listener);
0353         if (!$return) {
0354             return false;
0355         }
0356         if (!count($this->events[$event])) {
0357             unset($this->events[$event]);
0358         }
0359         return true;
0360     }
0361 
0362     /**
0363      * Detach a listener aggregate
0364      *
0365      * Listener aggregates accept an EventCollection instance, and call detach()
0366      * of all previously attached listeners.
0367      *
0368      * @param  Zend_EventManager_ListenerAggregate $aggregate
0369      * @return mixed return value of {@link Zend_EventManager_ListenerAggregate::detach()}
0370      */
0371     public function detachAggregate(Zend_EventManager_ListenerAggregate $aggregate)
0372     {
0373         return $aggregate->detach($this);
0374     }
0375 
0376     /**
0377      * Retrieve all registered events
0378      *
0379      * @return array
0380      */
0381     public function getEvents()
0382     {
0383         return array_keys($this->events);
0384     }
0385 
0386     /**
0387      * Retrieve all listeners for a given event
0388      *
0389      * @param  string $event
0390      * @return Zend_Stdlib_PriorityQueue
0391      */
0392     public function getListeners($event)
0393     {
0394         if (!array_key_exists($event, $this->events)) {
0395             return new Zend_Stdlib_PriorityQueue();
0396         }
0397         return $this->events[$event];
0398     }
0399 
0400     /**
0401      * Clear all listeners for a given event
0402      *
0403      * @param  string $event
0404      * @return void
0405      */
0406     public function clearListeners($event)
0407     {
0408         if (!empty($this->events[$event])) {
0409             unset($this->events[$event]);
0410         }
0411     }
0412 
0413     /**
0414      * Prepare arguments
0415      *
0416      * Use this method if you want to be able to modify arguments from within a
0417      * listener. It returns an ArrayObject of the arguments, which may then be
0418      * passed to trigger() or triggerUntil().
0419      *
0420      * @param  array $args
0421      * @return ArrayObject
0422      */
0423     public function prepareArgs(array $args)
0424     {
0425         return new ArrayObject($args);
0426     }
0427 
0428     /**
0429      * Trigger listeners
0430      *
0431      * Actual functionality for triggering listeners, to which both trigger() and triggerUntil()
0432      * delegate.
0433      *
0434      * @param  string           $event Event name
0435      * @param  EventDescription $e
0436      * @param  null|callback    $callback
0437      * @return ResponseCollection
0438      */
0439     protected function triggerListeners($event, Zend_EventManager_EventDescription $e, $callback = null)
0440     {
0441         $responses = new Zend_EventManager_ResponseCollection;
0442         $listeners = $this->getListeners($event);
0443 
0444         // Add shared/wildcard listeners to the list of listeners,
0445         // but don't modify the listeners object
0446         $sharedListeners         = $this->getSharedListeners($event);
0447         $sharedWildcardListeners = $this->getSharedListeners('*');
0448         $wildcardListeners       = $this->getListeners('*');
0449         if (count($sharedListeners) || count($sharedWildcardListeners) || count($wildcardListeners)) {
0450             $listeners = clone $listeners;
0451         }
0452 
0453         // Shared listeners on this specific event
0454         $this->insertListeners($listeners, $sharedListeners);
0455 
0456         // Shared wildcard listeners
0457         $this->insertListeners($listeners, $sharedWildcardListeners);
0458 
0459         // Add wildcard listeners
0460         $this->insertListeners($listeners, $wildcardListeners);
0461 
0462         if ($listeners->isEmpty()) {
0463             return $responses;
0464         }
0465 
0466         foreach ($listeners as $listener) {
0467             // Trigger the listener's callback, and push its result onto the
0468             // response collection
0469             $responses->push(call_user_func($listener->getCallback(), $e));
0470 
0471             // If the event was asked to stop propagating, do so
0472             if ($e->propagationIsStopped()) {
0473                 $responses->setStopped(true);
0474                 break;
0475             }
0476 
0477             // If the result causes our validation callback to return true,
0478             // stop propagation
0479             if ($callback && call_user_func($callback, $responses->last())) {
0480                 $responses->setStopped(true);
0481                 break;
0482             }
0483         }
0484 
0485         return $responses;
0486     }
0487 
0488     /**
0489      * Get list of all listeners attached to the shared collection for
0490      * identifiers registered by this instance
0491      *
0492      * @param  string $event
0493      * @return array
0494      */
0495     protected function getSharedListeners($event)
0496     {
0497         if (!$sharedCollections = $this->getSharedCollections()) {
0498             return array();
0499         }
0500 
0501         $identifiers     = $this->getIdentifiers();
0502         $sharedListeners = array();
0503 
0504         foreach ($identifiers as $id) {
0505             if (!$listeners = $sharedCollections->getListeners($id, $event)) {
0506                 continue;
0507             }
0508 
0509             if (!is_array($listeners) && !($listeners instanceof Traversable)) {
0510                 continue;
0511             }
0512 
0513             foreach ($listeners as $listener) {
0514                 if (!$listener instanceof Zend_Stdlib_CallbackHandler) {
0515                     continue;
0516                 }
0517                 $sharedListeners[] = $listener;
0518             }
0519         }
0520 
0521         return $sharedListeners;
0522     }
0523 
0524     /**
0525      * Add listeners to the master queue of listeners
0526      *
0527      * Used to inject shared listeners and wildcard listeners.
0528      * 
0529      * @param  Zend_Stdlib_PriorityQueue $masterListeners 
0530      * @param  Zend_Stdlib_PriorityQueue $listeners 
0531      * @return void
0532      */
0533     protected function insertListeners($masterListeners, $listeners)
0534     {
0535         if (!count($listeners)) {
0536             return;
0537         }
0538 
0539         foreach ($listeners as $listener) {
0540             $priority = $listener->getMetadatum('priority');
0541             if (null === $priority) {
0542                 $priority = 1;
0543             } elseif (is_array($priority)) {
0544                 // If we have an array, likely using PriorityQueue. Grab first
0545                 // element of the array, as that's the actual priority.
0546                 $priority = array_shift($priority);
0547             }
0548             $masterListeners->insert($listener, $priority);
0549         }
0550     }
0551 }