CakeEventManager.php 9.3 KB

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