EventManager.php 19 KB

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