web/lib/Zend/EventManager/EventManager.php
changeset 808 6b6c2214f778
child 1230 68c69c656a2c
equal deleted inserted replaced
807:877f952ae2bd 808:6b6c2214f778
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_EventManager
       
    17  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  */
       
    20 
       
    21 require_once 'Zend/EventManager/Event.php';
       
    22 require_once 'Zend/EventManager/EventCollection.php';
       
    23 require_once 'Zend/EventManager/ResponseCollection.php';
       
    24 require_once 'Zend/EventManager/SharedEventCollectionAware.php';
       
    25 require_once 'Zend/EventManager/StaticEventManager.php';
       
    26 require_once 'Zend/Stdlib/CallbackHandler.php';
       
    27 require_once 'Zend/Stdlib/PriorityQueue.php';
       
    28 
       
    29 /**
       
    30  * Event manager: notification system
       
    31  *
       
    32  * Use the EventManager when you want to create a per-instance notification
       
    33  * system for your objects.
       
    34  *
       
    35  * @category   Zend
       
    36  * @package    Zend_EventManager
       
    37  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
       
    38  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    39  */
       
    40 class Zend_EventManager_EventManager implements Zend_EventManager_EventCollection, Zend_EventManager_SharedEventCollectionAware
       
    41 {
       
    42     /**
       
    43      * Subscribed events and their listeners
       
    44      * @var array Array of Zend_Stdlib_PriorityQueue objects
       
    45      */
       
    46     protected $events = array();
       
    47 
       
    48     /**
       
    49      * @var string Class representing the event being emitted
       
    50      */
       
    51     protected $eventClass = 'Zend_EventManager_Event';
       
    52 
       
    53     /**
       
    54      * Identifiers, used to pull static signals from StaticEventManager
       
    55      * @var array
       
    56      */
       
    57     protected $identifiers = array();
       
    58 
       
    59     /**
       
    60      * Static collections
       
    61      * @var false|null|Zend_EventManager_StaticEventCollection
       
    62      */
       
    63     protected $sharedCollections = null;
       
    64 
       
    65     /**
       
    66      * Constructor
       
    67      *
       
    68      * Allows optionally specifying identifier(s) to use to pull signals from a
       
    69      * StaticEventManager.
       
    70      *
       
    71      * @param  null|string|int|array|Traversable $identifiers
       
    72      * @return void
       
    73      */
       
    74     public function __construct($identifiers = null)
       
    75     {
       
    76         $this->setIdentifiers($identifiers);
       
    77     }
       
    78 
       
    79     /**
       
    80      * Set the event class to utilize
       
    81      *
       
    82      * @param  string $class
       
    83      * @return Zend_EventManager_EventManager
       
    84      */
       
    85     public function setEventClass($class)
       
    86     {
       
    87         $this->eventClass = $class;
       
    88         return $this;
       
    89     }
       
    90 
       
    91     /**
       
    92      * Set static collections container
       
    93      *
       
    94      * @param  Zend_EventManager_StaticEventCollection $collections
       
    95      * @return void
       
    96      */
       
    97     public function setSharedCollections(Zend_EventManager_SharedEventCollection $collections)
       
    98     {
       
    99         $this->sharedCollections = $collections;
       
   100         return $this;
       
   101     }
       
   102 
       
   103     /**
       
   104      * Remove any shared collections
       
   105      *
       
   106      * Sets {@link $sharedCollections} to boolean false to disable ability
       
   107      * to lazy-load static event manager instance.
       
   108      * 
       
   109      * @return void
       
   110      */
       
   111     public function unsetSharedCollections()
       
   112     {
       
   113         $this->sharedCollections = false;
       
   114     }
       
   115 
       
   116     /**
       
   117      * Get static collections container
       
   118      *
       
   119      * @return false|Zend_EventManager_SharedEventCollection
       
   120      */
       
   121     public function getSharedCollections()
       
   122     {
       
   123         if (null === $this->sharedCollections) {
       
   124             $this->setSharedCollections(Zend_EventManager_StaticEventManager::getInstance());
       
   125         }
       
   126         return $this->sharedCollections;
       
   127     }
       
   128 
       
   129     /**
       
   130      * Get the identifier(s) for this Zend_EventManager_EventManager
       
   131      *
       
   132      * @return array
       
   133      */
       
   134     public function getIdentifiers()
       
   135     {
       
   136         return $this->identifiers;
       
   137     }
       
   138 
       
   139     /**
       
   140      * Set the identifiers (overrides any currently set identifiers)
       
   141      *
       
   142      * @param string|int|array|Traversable $identifiers
       
   143      * @return Zend_EventManager_EventManager
       
   144      */
       
   145     public function setIdentifiers($identifiers)
       
   146     {
       
   147         if (is_array($identifiers) || $identifiers instanceof Traversable) {
       
   148             $this->identifiers = array_unique((array) $identifiers);
       
   149         } elseif ($identifiers !== null) {
       
   150             $this->identifiers = array($identifiers);
       
   151         }
       
   152         return $this;
       
   153     }
       
   154 
       
   155     /**
       
   156      * Add some identifier(s) (appends to any currently set identifiers)
       
   157      *
       
   158      * @param string|int|array|Traversable $identifiers
       
   159      * @return Zend_EventManager_EventManager
       
   160      */
       
   161     public function addIdentifiers($identifiers)
       
   162     {
       
   163         if (is_array($identifiers) || $identifiers instanceof Traversable) {
       
   164             $this->identifiers = array_unique($this->identifiers + (array) $identifiers);
       
   165         } elseif ($identifiers !== null) {
       
   166             $this->identifiers = array_unique(array_merge($this->identifiers, array($identifiers)));
       
   167         }
       
   168         return $this;
       
   169     }
       
   170 
       
   171     /**
       
   172      * Trigger all listeners for a given event
       
   173      *
       
   174      * Can emulate triggerUntil() if the last argument provided is a callback.
       
   175      *
       
   176      * @param  string $event
       
   177      * @param  string|object $target Object calling emit, or symbol describing target (such as static method name)
       
   178      * @param  array|ArrayAccess $argv Array of arguments; typically, should be associative
       
   179      * @param  null|callback $callback
       
   180      * @return Zend_EventManager_ResponseCollection All listener return values
       
   181      */
       
   182     public function trigger($event, $target = null, $argv = array(), $callback = null)
       
   183     {
       
   184         if ($event instanceof Zend_EventManager_EventDescription) {
       
   185             $e        = $event;
       
   186             $event    = $e->getName();
       
   187             $callback = $target;
       
   188         } elseif ($target instanceof Zend_EventManager_EventDescription) {
       
   189             $e = $target;
       
   190             $e->setName($event);
       
   191             $callback = $argv;
       
   192         } elseif ($argv instanceof Zend_EventManager_EventDescription) {
       
   193             $e = $argv;
       
   194             $e->setName($event);
       
   195             $e->setTarget($target);
       
   196         } else {
       
   197             $e = new $this->eventClass();
       
   198             $e->setName($event);
       
   199             $e->setTarget($target);
       
   200             $e->setParams($argv);
       
   201         }
       
   202 
       
   203         if ($callback && !is_callable($callback)) {
       
   204             require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
       
   205             throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided');
       
   206         }
       
   207 
       
   208         return $this->triggerListeners($event, $e, $callback);
       
   209     }
       
   210 
       
   211     /**
       
   212      * Trigger listeners until return value of one causes a callback to
       
   213      * evaluate to true
       
   214      *
       
   215      * Triggers listeners until the provided callback evaluates the return
       
   216      * value of one as true, or until all listeners have been executed.
       
   217      *
       
   218      * @param  string $event
       
   219      * @param  string|object $target Object calling emit, or symbol describing target (such as static method name)
       
   220      * @param  array|ArrayAccess $argv Array of arguments; typically, should be associative
       
   221      * @param  Callable $callback
       
   222      * @throws Zend_Stdlib_Exception_InvalidCallbackException if invalid callback provided
       
   223      */
       
   224     public function triggerUntil($event, $target, $argv = null, $callback = null)
       
   225     {
       
   226         if ($event instanceof Zend_EventManager_EventDescription) {
       
   227             $e        = $event;
       
   228             $event    = $e->getName();
       
   229             $callback = $target;
       
   230         } elseif ($target instanceof Zend_EventManager_EventDescription) {
       
   231             $e = $target;
       
   232             $e->setName($event);
       
   233             $callback = $argv;
       
   234         } elseif ($argv instanceof Zend_EventManager_EventDescription) {
       
   235             $e = $argv;
       
   236             $e->setName($event);
       
   237             $e->setTarget($target);
       
   238         } else {
       
   239             $e = new $this->eventClass();
       
   240             $e->setName($event);
       
   241             $e->setTarget($target);
       
   242             $e->setParams($argv);
       
   243         }
       
   244 
       
   245         if (!is_callable($callback)) {
       
   246             require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
       
   247             throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided');
       
   248         }
       
   249 
       
   250         return $this->triggerListeners($event, $e, $callback);
       
   251     }
       
   252 
       
   253     /**
       
   254      * Attach a listener to an event
       
   255      *
       
   256      * The first argument is the event, and the next argument describes a
       
   257      * callback that will respond to that event. A CallbackHandler instance
       
   258      * describing the event listener combination will be returned.
       
   259      *
       
   260      * The last argument indicates a priority at which the event should be
       
   261      * executed. By default, this value is 1; however, you may set it for any
       
   262      * integer value. Higher values have higher priority (i.e., execute first).
       
   263      *
       
   264      * You can specify "*" for the event name. In such cases, the listener will 
       
   265      * be triggered for every event.
       
   266      *
       
   267      * @param  string|array|Zend_EventManager_ListenerAggregate $event An event or array of event names. If a ListenerAggregate, proxies to {@link attachAggregate()}.
       
   268      * @param  callback|int $callback If string $event provided, expects PHP callback; for a ListenerAggregate $event, this will be the priority
       
   269      * @param  int $priority If provided, the priority at which to register the callback
       
   270      * @return Zend_Stdlib_CallbackHandler|mixed CallbackHandler if attaching callback (to allow later unsubscribe); mixed if attaching aggregate
       
   271      */
       
   272     public function attach($event, $callback = null, $priority = 1)
       
   273     {
       
   274         // Proxy ListenerAggregate arguments to attachAggregate()
       
   275         if ($event instanceof Zend_EventManager_ListenerAggregate) {
       
   276             return $this->attachAggregate($event, $callback);
       
   277         }
       
   278 
       
   279         // Null callback is invalid
       
   280         if (null === $callback) {
       
   281             require_once 'Zend/EventManager/Exception/InvalidArgumentException.php';
       
   282             throw new Zend_EventManager_Exception_InvalidArgumentException(sprintf(
       
   283                 '%s: expects a callback; none provided',
       
   284                 __METHOD__
       
   285             ));
       
   286         }
       
   287 
       
   288         // Array of events should be registered individually, and return an array of all listeners
       
   289         if (is_array($event)) {
       
   290             $listeners = array();
       
   291             foreach ($event as $name) {
       
   292                 $listeners[] = $this->attach($name, $callback, $priority);
       
   293             }
       
   294             return $listeners;
       
   295         }
       
   296 
       
   297         // If we don't have a priority queue for the event yet, create one
       
   298         if (empty($this->events[$event])) {
       
   299             $this->events[$event] = new Zend_Stdlib_PriorityQueue();
       
   300         }
       
   301 
       
   302         // Create a callback handler, setting the event and priority in its metadata
       
   303         $listener = new Zend_Stdlib_CallbackHandler($callback, array('event' => $event, 'priority' => $priority));
       
   304 
       
   305         // Inject the callback handler into the queue
       
   306         $this->events[$event]->insert($listener, $priority);
       
   307         return $listener;
       
   308     }
       
   309 
       
   310     /**
       
   311      * Attach a listener aggregate
       
   312      *
       
   313      * Listener aggregates accept an EventCollection instance, and call attach()
       
   314      * one or more times, typically to attach to multiple events using local
       
   315      * methods.
       
   316      *
       
   317      * @param  Zend_EventManager_ListenerAggregate $aggregate
       
   318      * @param  int $priority If provided, a suggested priority for the aggregate to use
       
   319      * @return mixed return value of {@link Zend_EventManager_ListenerAggregate::attach()}
       
   320      */
       
   321     public function attachAggregate(Zend_EventManager_ListenerAggregate $aggregate, $priority = 1)
       
   322     {
       
   323         return $aggregate->attach($this, $priority);
       
   324     }
       
   325 
       
   326     /**
       
   327      * Unsubscribe a listener from an event
       
   328      *
       
   329      * @param  Zend_Stdlib_CallbackHandler|Zend_EventManager_ListenerAggregate $listener
       
   330      * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
       
   331      * @throws Zend_EventManager_Exception_InvalidArgumentException if invalid listener provided
       
   332      */
       
   333     public function detach($listener)
       
   334     {
       
   335         if ($listener instanceof Zend_EventManager_ListenerAggregate) {
       
   336             return $this->detachAggregate($listener);
       
   337         }
       
   338 
       
   339         if (!$listener instanceof Zend_Stdlib_CallbackHandler) {
       
   340             require_once 'Zend/EventManager/Exception/InvalidArgumentException.php';
       
   341             throw new Zend_EventManager_Exception_InvalidArgumentException(sprintf(
       
   342                 '%s: expected a Zend_EventManager_ListenerAggregate or Zend_Stdlib_CallbackHandler; received "%s"',
       
   343                 __METHOD__,
       
   344                 (is_object($listener) ? get_class($listener) : gettype($listener))
       
   345             ));
       
   346         }
       
   347 
       
   348         $event = $listener->getMetadatum('event');
       
   349         if (!$event || empty($this->events[$event])) {
       
   350             return false;
       
   351         }
       
   352         $return = $this->events[$event]->remove($listener);
       
   353         if (!$return) {
       
   354             return false;
       
   355         }
       
   356         if (!count($this->events[$event])) {
       
   357             unset($this->events[$event]);
       
   358         }
       
   359         return true;
       
   360     }
       
   361 
       
   362     /**
       
   363      * Detach a listener aggregate
       
   364      *
       
   365      * Listener aggregates accept an EventCollection instance, and call detach()
       
   366      * of all previously attached listeners.
       
   367      *
       
   368      * @param  Zend_EventManager_ListenerAggregate $aggregate
       
   369      * @return mixed return value of {@link Zend_EventManager_ListenerAggregate::detach()}
       
   370      */
       
   371     public function detachAggregate(Zend_EventManager_ListenerAggregate $aggregate)
       
   372     {
       
   373         return $aggregate->detach($this);
       
   374     }
       
   375 
       
   376     /**
       
   377      * Retrieve all registered events
       
   378      *
       
   379      * @return array
       
   380      */
       
   381     public function getEvents()
       
   382     {
       
   383         return array_keys($this->events);
       
   384     }
       
   385 
       
   386     /**
       
   387      * Retrieve all listeners for a given event
       
   388      *
       
   389      * @param  string $event
       
   390      * @return Zend_Stdlib_PriorityQueue
       
   391      */
       
   392     public function getListeners($event)
       
   393     {
       
   394         if (!array_key_exists($event, $this->events)) {
       
   395             return new Zend_Stdlib_PriorityQueue();
       
   396         }
       
   397         return $this->events[$event];
       
   398     }
       
   399 
       
   400     /**
       
   401      * Clear all listeners for a given event
       
   402      *
       
   403      * @param  string $event
       
   404      * @return void
       
   405      */
       
   406     public function clearListeners($event)
       
   407     {
       
   408         if (!empty($this->events[$event])) {
       
   409             unset($this->events[$event]);
       
   410         }
       
   411     }
       
   412 
       
   413     /**
       
   414      * Prepare arguments
       
   415      *
       
   416      * Use this method if you want to be able to modify arguments from within a
       
   417      * listener. It returns an ArrayObject of the arguments, which may then be
       
   418      * passed to trigger() or triggerUntil().
       
   419      *
       
   420      * @param  array $args
       
   421      * @return ArrayObject
       
   422      */
       
   423     public function prepareArgs(array $args)
       
   424     {
       
   425         return new ArrayObject($args);
       
   426     }
       
   427 
       
   428     /**
       
   429      * Trigger listeners
       
   430      *
       
   431      * Actual functionality for triggering listeners, to which both trigger() and triggerUntil()
       
   432      * delegate.
       
   433      *
       
   434      * @param  string           $event Event name
       
   435      * @param  EventDescription $e
       
   436      * @param  null|callback    $callback
       
   437      * @return ResponseCollection
       
   438      */
       
   439     protected function triggerListeners($event, Zend_EventManager_EventDescription $e, $callback = null)
       
   440     {
       
   441         $responses = new Zend_EventManager_ResponseCollection;
       
   442         $listeners = $this->getListeners($event);
       
   443 
       
   444         // Add shared/wildcard listeners to the list of listeners,
       
   445         // but don't modify the listeners object
       
   446         $sharedListeners         = $this->getSharedListeners($event);
       
   447         $sharedWildcardListeners = $this->getSharedListeners('*');
       
   448         $wildcardListeners       = $this->getListeners('*');
       
   449         if (count($sharedListeners) || count($sharedWildcardListeners) || count($wildcardListeners)) {
       
   450             $listeners = clone $listeners;
       
   451         }
       
   452 
       
   453         // Shared listeners on this specific event
       
   454         $this->insertListeners($listeners, $sharedListeners);
       
   455 
       
   456         // Shared wildcard listeners
       
   457         $this->insertListeners($listeners, $sharedWildcardListeners);
       
   458 
       
   459         // Add wildcard listeners
       
   460         $this->insertListeners($listeners, $wildcardListeners);
       
   461 
       
   462         if ($listeners->isEmpty()) {
       
   463             return $responses;
       
   464         }
       
   465 
       
   466         foreach ($listeners as $listener) {
       
   467             // Trigger the listener's callback, and push its result onto the
       
   468             // response collection
       
   469             $responses->push(call_user_func($listener->getCallback(), $e));
       
   470 
       
   471             // If the event was asked to stop propagating, do so
       
   472             if ($e->propagationIsStopped()) {
       
   473                 $responses->setStopped(true);
       
   474                 break;
       
   475             }
       
   476 
       
   477             // If the result causes our validation callback to return true,
       
   478             // stop propagation
       
   479             if ($callback && call_user_func($callback, $responses->last())) {
       
   480                 $responses->setStopped(true);
       
   481                 break;
       
   482             }
       
   483         }
       
   484 
       
   485         return $responses;
       
   486     }
       
   487 
       
   488     /**
       
   489      * Get list of all listeners attached to the shared collection for
       
   490      * identifiers registered by this instance
       
   491      *
       
   492      * @param  string $event
       
   493      * @return array
       
   494      */
       
   495     protected function getSharedListeners($event)
       
   496     {
       
   497         if (!$sharedCollections = $this->getSharedCollections()) {
       
   498             return array();
       
   499         }
       
   500 
       
   501         $identifiers     = $this->getIdentifiers();
       
   502         $sharedListeners = array();
       
   503 
       
   504         foreach ($identifiers as $id) {
       
   505             if (!$listeners = $sharedCollections->getListeners($id, $event)) {
       
   506                 continue;
       
   507             }
       
   508 
       
   509             if (!is_array($listeners) && !($listeners instanceof Traversable)) {
       
   510                 continue;
       
   511             }
       
   512 
       
   513             foreach ($listeners as $listener) {
       
   514                 if (!$listener instanceof Zend_Stdlib_CallbackHandler) {
       
   515                     continue;
       
   516                 }
       
   517                 $sharedListeners[] = $listener;
       
   518             }
       
   519         }
       
   520 
       
   521         return $sharedListeners;
       
   522     }
       
   523 
       
   524     /**
       
   525      * Add listeners to the master queue of listeners
       
   526      *
       
   527      * Used to inject shared listeners and wildcard listeners.
       
   528      * 
       
   529      * @param  Zend_Stdlib_PriorityQueue $masterListeners 
       
   530      * @param  Zend_Stdlib_PriorityQueue $listeners 
       
   531      * @return void
       
   532      */
       
   533     protected function insertListeners($masterListeners, $listeners)
       
   534     {
       
   535         if (!count($listeners)) {
       
   536             return;
       
   537         }
       
   538 
       
   539         foreach ($listeners as $listener) {
       
   540             $priority = $listener->getMetadatum('priority');
       
   541             if (null === $priority) {
       
   542                 $priority = 1;
       
   543             } elseif (is_array($priority)) {
       
   544                 // If we have an array, likely using PriorityQueue. Grab first
       
   545                 // element of the array, as that's the actual priority.
       
   546                 $priority = array_shift($priority);
       
   547             }
       
   548             $masterListeners->insert($listener, $priority);
       
   549         }
       
   550     }
       
   551 }