EventManager.php 18 KB

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