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