EventManager.php 9.9 KB

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