CakeEventManager.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /**
  3. *
  4. * PHP 5
  5. *
  6. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  7. * Copyright 2005-2011, 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-2011, 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 and pass the correct
  21. * data to them, and fire them in the correct order, when associated events are triggered. You
  22. * can create multiple instances of this objects 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 sigleton
  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 a first params, 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 instances 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 mixed $eventKey The event unique identifier name to with 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 into 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. */
  91. public function attach($callable, $eventKey = null, $options = array()) {
  92. if (!$eventKey && !($callable instanceof CakeEventListener)) {
  93. throw new InvalidArgumentException(__d('cake_dev', 'The eventKey variable is required'));
  94. }
  95. if ($callable instanceof CakeEventListener) {
  96. $this->_attachSubscriber($callable);
  97. return;
  98. }
  99. $options = $options + array('priority' => self::$defaultPriority, 'passParams' => false);
  100. $this->_listeners[$eventKey][$options['priority']][] = array(
  101. 'callable' => $callable,
  102. 'passParams' => $options['passParams'],
  103. );
  104. }
  105. /**
  106. * Auxiliary function to attach all implemented callbacks of a CakeEventListener class instance
  107. * as individual methods on this manager
  108. *
  109. * @param CakeEventListener $subscriber
  110. * @return void
  111. */
  112. protected function _attachSubscriber(CakeEventListener $subscriber) {
  113. foreach ($subscriber->implementedEvents() as $eventKey => $function) {
  114. $options = array();
  115. $method = $function;
  116. if (is_array($function) && isset($function['callable'])) {
  117. list($method, $options) = $this->_extractCallable($function, $subscriber);
  118. } else if (is_array($function) && is_numeric(key($function))) {
  119. foreach ($function as $f) {
  120. list($method, $options) = $this->_extractCallable($f, $subscriber);
  121. $this->attach($method, $eventKey, $options);
  122. }
  123. continue;
  124. }
  125. if (is_string($method)) {
  126. $method = array($subscriber, $function);
  127. }
  128. $this->attach($method, $eventKey, $options);
  129. }
  130. }
  131. /**
  132. * Auxiliary function to extract and return a PHP callback type out of the callable definition
  133. * from the return value of the `implementedEvents` method on a CakeEventListener
  134. *
  135. * @param array $function the array taken from a handler definition for a event
  136. * @param CakeEventListener $object The handler object
  137. * @return callback
  138. */
  139. protected function _extractCallable($function, $object) {
  140. $method = $function['callable'];
  141. $options = $function;
  142. unset($options['callable']);
  143. if (is_string($method)) {
  144. $method = array($object, $method);
  145. }
  146. return array($method, $options);
  147. }
  148. /**
  149. * Removes a listener from the active listeners.
  150. *
  151. * @param callback|CakeEventListener $callable any valid PHP callback type or an instance of CakeEventListener
  152. * @return void
  153. */
  154. public function detach($callable, $eventKey = null) {
  155. if ($callable instanceof CakeEventListener) {
  156. return $this->_detachSubscriber($callable, $eventKey);
  157. }
  158. if (empty($eventKey)) {
  159. foreach (array_keys($this->_listeners) as $eventKey) {
  160. $this->detach($callable, $eventKey);
  161. }
  162. return;
  163. }
  164. if (empty($this->_listeners[$eventKey])) {
  165. return;
  166. }
  167. foreach ($this->_listeners[$eventKey] as $priority => $callables) {
  168. foreach ($callables as $k => $callback) {
  169. if ($callback['callable'] === $callable) {
  170. unset($this->_listeners[$eventKey][$priority][$k]);
  171. break;
  172. }
  173. }
  174. }
  175. }
  176. /**
  177. * Auxiliary function to help detach all listeners provided by an object implementing CakeEventListener
  178. *
  179. * @param CakeEventListener $subscriber the subscriber to be detached
  180. * @param string $eventKey optional event key name to unsubscribe the listener from
  181. * @return void
  182. */
  183. protected function _detachSubscriber(CakeEventListener $subscriber, $eventKey = null) {
  184. $events = $subscriber->implementedEvents();
  185. if (!empty($eventKey) && empty($events[$eventKey])) {
  186. return;
  187. } else if (!empty($eventKey)) {
  188. $events = array($eventKey => $events[$eventKey]);
  189. }
  190. foreach ($events as $key => $function) {
  191. if (is_array($function)) {
  192. if (is_numeric(key($function))) {
  193. foreach ($function as $handler) {
  194. $handler = isset($handler['callable']) ? $handler['callable'] : $handler;
  195. $this->detach(array($subscriber, $handler), $key);
  196. }
  197. continue;
  198. }
  199. $function = $function['callable'];
  200. }
  201. $this->detach(array($subscriber, $function), $key);
  202. }
  203. }
  204. /**
  205. * Dispatches a new event to all configured listeners
  206. *
  207. * @param mixed $event the event key name or instance of CakeEvent
  208. * @return void
  209. */
  210. public function dispatch($event) {
  211. if (is_string($event)) {
  212. $event = new CakeEvent($event);
  213. }
  214. if (!$this->_isGlobal) {
  215. self::instance()->dispatch($event);
  216. }
  217. if (empty($this->_listeners[$event->name()])) {
  218. return;
  219. }
  220. foreach ($this->listeners($event->name()) as $listener) {
  221. if ($event->isStopped()) {
  222. break;
  223. }
  224. if ($listener['passParams'] === true) {
  225. $result = call_user_func_array($listener['callable'], $event->data);
  226. } else {
  227. $result = call_user_func($listener['callable'], $event);
  228. }
  229. if ($result === false) {
  230. $event->stopPropagation();
  231. }
  232. if ($result !== null) {
  233. $event->result = $result;
  234. }
  235. continue;
  236. }
  237. }
  238. /**
  239. * Returns a list of all listeners for a eventKey in the order they should be called
  240. *
  241. * @param string $eventKey
  242. * @return array
  243. */
  244. public function listeners($eventKey) {
  245. if (empty($this->_listeners[$eventKey])) {
  246. return array();
  247. }
  248. ksort($this->_listeners[$eventKey]);
  249. $result = array();
  250. foreach ($this->_listeners[$eventKey] as $priorityQ) {
  251. $result = array_merge($result, $priorityQ);
  252. }
  253. return $result;
  254. }
  255. }