CakeEventManager.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. /**
  3. *
  4. * PHP 5
  5. *
  6. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  7. * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  8. *
  9. * Licensed under The MIT License
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://cakephp.org CakePHP(tm) Project
  14. * @package Cake.Event
  15. * @since CakePHP(tm) v 2.1
  16. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  17. */
  18. App::uses('CakeEventListener', 'Event');
  19. /**
  20. * The event manager is responsible for keeping track of event listeners, passing the correct
  21. * data to them, and firing them in the correct order, when associated events are triggered. You
  22. * can create multiple instances of this object to manage local events or keep a single instance
  23. * and pass it around to manage all events in your app.
  24. *
  25. * @package Cake.Event
  26. */
  27. class CakeEventManager {
  28. /**
  29. * The default priority queue value for new, attached listeners
  30. *
  31. * @var int
  32. */
  33. public static $defaultPriority = 10;
  34. /**
  35. * The globally available instance, used for dispatching events attached from any scope
  36. *
  37. * @var CakeEventManager
  38. */
  39. protected static $_generalManager = null;
  40. /**
  41. * List of listener callbacks associated to
  42. *
  43. * @var object $Listeners
  44. */
  45. protected $_listeners = array();
  46. /**
  47. * Internal flag to distinguish a common manager from the singleton
  48. *
  49. * @var boolean
  50. */
  51. protected $_isGlobal = false;
  52. /**
  53. * Returns the globally available instance of a CakeEventManager
  54. * this is used for dispatching events attached from outside the scope
  55. * other managers were created. Usually for creating hook systems or inter-class
  56. * communication
  57. *
  58. * If called with the first parameter, it will be set as the globally available instance
  59. *
  60. * @param CakeEventManager $manager
  61. * @return CakeEventManager the global event manager
  62. */
  63. public static function instance($manager = null) {
  64. if ($manager instanceof CakeEventManager) {
  65. self::$_generalManager = $manager;
  66. }
  67. if (empty(self::$_generalManager)) {
  68. self::$_generalManager = new CakeEventManager;
  69. }
  70. self::$_generalManager->_isGlobal = true;
  71. return self::$_generalManager;
  72. }
  73. /**
  74. * Adds a new listener to an event. Listeners
  75. *
  76. * @param callback|CakeEventListener $callable PHP valid callback type or instance of CakeEventListener to be called
  77. * when the event named with $eventKey is triggered. If a CakeEventListener instance is passed, then the `implementedEvents`
  78. * method will be called on the object to register the declared events individually as methods to be managed by this class.
  79. * It is possible to define multiple event handlers per event name.
  80. *
  81. * @param string $eventKey The event unique identifier name with which the callback will be associated. If $callable
  82. * is an instance of CakeEventListener this argument will be ignored
  83. *
  84. * @param array $options used to set the `priority` and `passParams` flags to the listener.
  85. * Priorities are handled like queues, and multiple attachments added to the same priority queue will be treated in
  86. * the order of insertion. `passParams` means that the event data property will be converted to function arguments
  87. * when the listener is called. If $called is an instance of CakeEventListener, this parameter will be ignored
  88. *
  89. * @return void
  90. * @throws InvalidArgumentException When event key is missing or callable is not an
  91. * instance of CakeEventListener.
  92. */
  93. public function attach($callable, $eventKey = null, $options = array()) {
  94. if (!$eventKey && !($callable instanceof CakeEventListener)) {
  95. throw new InvalidArgumentException(__d('cake_dev', 'The eventKey variable is required'));
  96. }
  97. if ($callable instanceof CakeEventListener) {
  98. $this->_attachSubscriber($callable);
  99. return;
  100. }
  101. $options = $options + array('priority' => self::$defaultPriority, 'passParams' => false);
  102. $this->_listeners[$eventKey][$options['priority']][] = array(
  103. 'callable' => $callable,
  104. 'passParams' => $options['passParams'],
  105. );
  106. }
  107. /**
  108. * Auxiliary function to attach all implemented callbacks of a CakeEventListener class instance
  109. * as individual methods on this manager
  110. *
  111. * @param CakeEventListener $subscriber
  112. * @return void
  113. */
  114. protected function _attachSubscriber(CakeEventListener $subscriber) {
  115. foreach ($subscriber->implementedEvents() as $eventKey => $function) {
  116. $options = array();
  117. $method = $function;
  118. if (is_array($function) && isset($function['callable'])) {
  119. list($method, $options) = $this->_extractCallable($function, $subscriber);
  120. } elseif (is_array($function) && is_numeric(key($function))) {
  121. foreach ($function as $f) {
  122. list($method, $options) = $this->_extractCallable($f, $subscriber);
  123. $this->attach($method, $eventKey, $options);
  124. }
  125. continue;
  126. }
  127. if (is_string($method)) {
  128. $method = array($subscriber, $function);
  129. }
  130. $this->attach($method, $eventKey, $options);
  131. }
  132. }
  133. /**
  134. * Auxiliary function to extract and return a PHP callback type out of the callable definition
  135. * from the return value of the `implementedEvents` method on a CakeEventListener
  136. *
  137. * @param array $function the array taken from a handler definition for an event
  138. * @param CakeEventListener $object The handler object
  139. * @return callback
  140. */
  141. protected function _extractCallable($function, $object) {
  142. $method = $function['callable'];
  143. $options = $function;
  144. unset($options['callable']);
  145. if (is_string($method)) {
  146. $method = array($object, $method);
  147. }
  148. return array($method, $options);
  149. }
  150. /**
  151. * Removes a listener from the active listeners.
  152. *
  153. * @param callback|CakeEventListener $callable any valid PHP callback type or an instance of CakeEventListener
  154. * @param string $eventKey The event unique identifier name with which the callback has been associated
  155. * @return void
  156. */
  157. public function detach($callable, $eventKey = null) {
  158. if ($callable instanceof CakeEventListener) {
  159. return $this->_detachSubscriber($callable, $eventKey);
  160. }
  161. if (empty($eventKey)) {
  162. foreach (array_keys($this->_listeners) as $eventKey) {
  163. $this->detach($callable, $eventKey);
  164. }
  165. return;
  166. }
  167. if (empty($this->_listeners[$eventKey])) {
  168. return;
  169. }
  170. foreach ($this->_listeners[$eventKey] as $priority => $callables) {
  171. foreach ($callables as $k => $callback) {
  172. if ($callback['callable'] === $callable) {
  173. unset($this->_listeners[$eventKey][$priority][$k]);
  174. break;
  175. }
  176. }
  177. }
  178. }
  179. /**
  180. * Auxiliary function to help detach all listeners provided by an object implementing CakeEventListener
  181. *
  182. * @param CakeEventListener $subscriber the subscriber to be detached
  183. * @param string $eventKey optional event key name to unsubscribe the listener from
  184. * @return void
  185. */
  186. protected function _detachSubscriber(CakeEventListener $subscriber, $eventKey = null) {
  187. $events = $subscriber->implementedEvents();
  188. if (!empty($eventKey) && empty($events[$eventKey])) {
  189. return;
  190. } elseif (!empty($eventKey)) {
  191. $events = array($eventKey => $events[$eventKey]);
  192. }
  193. foreach ($events as $key => $function) {
  194. if (is_array($function)) {
  195. if (is_numeric(key($function))) {
  196. foreach ($function as $handler) {
  197. $handler = isset($handler['callable']) ? $handler['callable'] : $handler;
  198. $this->detach(array($subscriber, $handler), $key);
  199. }
  200. continue;
  201. }
  202. $function = $function['callable'];
  203. }
  204. $this->detach(array($subscriber, $function), $key);
  205. }
  206. }
  207. /**
  208. * Dispatches a new event to all configured listeners
  209. *
  210. * @param string|CakeEvent $event the event key name or instance of CakeEvent
  211. * @return void
  212. */
  213. public function dispatch($event) {
  214. if (is_string($event)) {
  215. $event = new CakeEvent($event);
  216. }
  217. if (!$this->_isGlobal) {
  218. self::instance()->dispatch($event);
  219. }
  220. if (empty($this->_listeners[$event->name()])) {
  221. return;
  222. }
  223. foreach ($this->listeners($event->name()) as $listener) {
  224. if ($event->isStopped()) {
  225. break;
  226. }
  227. if ($listener['passParams'] === true) {
  228. $result = call_user_func_array($listener['callable'], $event->data);
  229. } else {
  230. $result = call_user_func($listener['callable'], $event);
  231. }
  232. if ($result === false) {
  233. $event->stopPropagation();
  234. }
  235. if ($result !== null) {
  236. $event->result = $result;
  237. }
  238. continue;
  239. }
  240. }
  241. /**
  242. * Returns a list of all listeners for an eventKey in the order they should be called
  243. *
  244. * @param string $eventKey
  245. * @return array
  246. */
  247. public function listeners($eventKey) {
  248. if (empty($this->_listeners[$eventKey])) {
  249. return array();
  250. }
  251. ksort($this->_listeners[$eventKey]);
  252. $result = array();
  253. foreach ($this->_listeners[$eventKey] as $priorityQ) {
  254. $result = array_merge($result, $priorityQ);
  255. }
  256. return $result;
  257. }
  258. }