1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\Event;
16:
17: use InvalidArgumentException;
18:
19: 20: 21: 22: 23: 24:
25: class EventManager implements EventManagerInterface
26: {
27:
28: 29: 30: 31: 32:
33: public static $defaultPriority = 10;
34:
35: 36: 37: 38: 39:
40: protected static $_generalManager;
41:
42: 43: 44: 45: 46:
47: protected $_listeners = [];
48:
49: 50: 51: 52: 53:
54: protected $_isGlobal = false;
55:
56: 57: 58: 59: 60:
61: protected $_eventList;
62:
63: 64: 65: 66: 67:
68: protected $_trackEvents = false;
69:
70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80:
81: public static function instance($manager = null)
82: {
83: if ($manager instanceof EventManager) {
84: static::$_generalManager = $manager;
85: }
86: if (empty(static::$_generalManager)) {
87: static::$_generalManager = new static();
88: }
89:
90: static::$_generalManager->_isGlobal = true;
91:
92: return static::$_generalManager;
93: }
94:
95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114:
115: public function attach($callable, $eventKey = null, array $options = [])
116: {
117: deprecationWarning('EventManager::attach() is deprecated. Use EventManager::on() instead.');
118: if ($eventKey === null) {
119: $this->on($callable);
120:
121: return;
122: }
123: if ($options) {
124: $this->on($eventKey, $options, $callable);
125:
126: return;
127: }
128: $this->on($eventKey, $callable);
129: }
130:
131: 132: 133:
134: public function on($eventKey = null, $options = [], $callable = null)
135: {
136: if ($eventKey instanceof EventListenerInterface) {
137: $this->_attachSubscriber($eventKey);
138:
139: return $this;
140: }
141: $argCount = func_num_args();
142: if ($argCount === 2) {
143: $this->_listeners[$eventKey][static::$defaultPriority][] = [
144: 'callable' => $options
145: ];
146:
147: return $this;
148: }
149: if ($argCount === 3) {
150: $priority = isset($options['priority']) ? $options['priority'] : static::$defaultPriority;
151: $this->_listeners[$eventKey][$priority][] = [
152: 'callable' => $callable
153: ];
154:
155: return $this;
156: }
157: throw new InvalidArgumentException(
158: 'Invalid arguments for EventManager::on(). ' .
159: "Expected 1, 2 or 3 arguments. Got {$argCount} arguments."
160: );
161: }
162:
163: 164: 165: 166: 167: 168: 169:
170: protected function _attachSubscriber(EventListenerInterface $subscriber)
171: {
172: foreach ((array)$subscriber->implementedEvents() as $eventKey => $function) {
173: $options = [];
174: $method = $function;
175: if (is_array($function) && isset($function['callable'])) {
176: list($method, $options) = $this->_extractCallable($function, $subscriber);
177: } elseif (is_array($function) && is_numeric(key($function))) {
178: foreach ($function as $f) {
179: list($method, $options) = $this->_extractCallable($f, $subscriber);
180: $this->on($eventKey, $options, $method);
181: }
182: continue;
183: }
184: if (is_string($method)) {
185: $method = [$subscriber, $function];
186: }
187: $this->on($eventKey, $options, $method);
188: }
189: }
190:
191: 192: 193: 194: 195: 196: 197: 198:
199: protected function _extractCallable($function, $object)
200: {
201: $method = $function['callable'];
202: $options = $function;
203: unset($options['callable']);
204: if (is_string($method)) {
205: $method = [$object, $method];
206: }
207:
208: return [$method, $options];
209: }
210:
211: 212: 213: 214: 215: 216: 217: 218:
219: public function detach($callable, $eventKey = null)
220: {
221: deprecationWarning('EventManager::detach() is deprecated. Use EventManager::off() instead.');
222: if ($eventKey === null) {
223: $this->off($callable);
224:
225: return;
226: }
227: $this->off($eventKey, $callable);
228: }
229:
230: 231: 232:
233: public function off($eventKey, $callable = null)
234: {
235: if ($eventKey instanceof EventListenerInterface) {
236: $this->_detachSubscriber($eventKey);
237:
238: return $this;
239: }
240: if ($callable instanceof EventListenerInterface) {
241: $this->_detachSubscriber($callable, $eventKey);
242:
243: return $this;
244: }
245: if ($callable === null && is_string($eventKey)) {
246: unset($this->_listeners[$eventKey]);
247:
248: return $this;
249: }
250: if ($callable === null) {
251: foreach (array_keys($this->_listeners) as $name) {
252: $this->off($name, $eventKey);
253: }
254:
255: return $this;
256: }
257: if (empty($this->_listeners[$eventKey])) {
258: return $this;
259: }
260: foreach ($this->_listeners[$eventKey] as $priority => $callables) {
261: foreach ($callables as $k => $callback) {
262: if ($callback['callable'] === $callable) {
263: unset($this->_listeners[$eventKey][$priority][$k]);
264: break;
265: }
266: }
267: }
268:
269: return $this;
270: }
271:
272: 273: 274: 275: 276: 277: 278:
279: protected function _detachSubscriber(EventListenerInterface $subscriber, $eventKey = null)
280: {
281: $events = (array)$subscriber->implementedEvents();
282: if (!empty($eventKey) && empty($events[$eventKey])) {
283: return;
284: }
285: if (!empty($eventKey)) {
286: $events = [$eventKey => $events[$eventKey]];
287: }
288: foreach ($events as $key => $function) {
289: if (is_array($function)) {
290: if (is_numeric(key($function))) {
291: foreach ($function as $handler) {
292: $handler = isset($handler['callable']) ? $handler['callable'] : $handler;
293: $this->off($key, [$subscriber, $handler]);
294: }
295: continue;
296: }
297: $function = $function['callable'];
298: }
299: $this->off($key, [$subscriber, $function]);
300: }
301: }
302:
303: 304: 305:
306: public function dispatch($event)
307: {
308: if (is_string($event)) {
309: $event = new Event($event);
310: }
311:
312: $listeners = $this->listeners($event->getName());
313:
314: if ($this->_trackEvents) {
315: $this->addEventToList($event);
316: }
317:
318: if (!$this->_isGlobal && static::instance()->isTrackingEvents()) {
319: static::instance()->addEventToList($event);
320: }
321:
322: if (empty($listeners)) {
323: return $event;
324: }
325:
326: foreach ($listeners as $listener) {
327: if ($event->isStopped()) {
328: break;
329: }
330: $result = $this->_callListener($listener['callable'], $event);
331: if ($result === false) {
332: $event->stopPropagation();
333: }
334: if ($result !== null) {
335: $event->setResult($result);
336: }
337: }
338:
339: return $event;
340: }
341:
342: 343: 344: 345: 346: 347: 348:
349: protected function _callListener(callable $listener, Event $event)
350: {
351: $data = $event->getData();
352:
353: return $listener($event, ...array_values($data));
354: }
355:
356: 357: 358:
359: public function listeners($eventKey)
360: {
361: $localListeners = [];
362: if (!$this->_isGlobal) {
363: $localListeners = $this->prioritisedListeners($eventKey);
364: $localListeners = empty($localListeners) ? [] : $localListeners;
365: }
366: $globalListeners = static::instance()->prioritisedListeners($eventKey);
367: $globalListeners = empty($globalListeners) ? [] : $globalListeners;
368:
369: $priorities = array_merge(array_keys($globalListeners), array_keys($localListeners));
370: $priorities = array_unique($priorities);
371: asort($priorities);
372:
373: $result = [];
374: foreach ($priorities as $priority) {
375: if (isset($globalListeners[$priority])) {
376: $result = array_merge($result, $globalListeners[$priority]);
377: }
378: if (isset($localListeners[$priority])) {
379: $result = array_merge($result, $localListeners[$priority]);
380: }
381: }
382:
383: return $result;
384: }
385:
386: 387: 388: 389: 390: 391:
392: public function prioritisedListeners($eventKey)
393: {
394: if (empty($this->_listeners[$eventKey])) {
395: return [];
396: }
397:
398: return $this->_listeners[$eventKey];
399: }
400:
401: 402: 403: 404: 405: 406:
407: public function matchingListeners($eventKeyPattern)
408: {
409: $matchPattern = '/' . preg_quote($eventKeyPattern, '/') . '/';
410: $matches = array_intersect_key(
411: $this->_listeners,
412: array_flip(
413: preg_grep($matchPattern, array_keys($this->_listeners), 0)
414: )
415: );
416:
417: return $matches;
418: }
419:
420: 421: 422: 423: 424:
425: public function getEventList()
426: {
427: return $this->_eventList;
428: }
429:
430: 431: 432: 433: 434: 435:
436: public function addEventToList(Event $event)
437: {
438: if ($this->_eventList) {
439: $this->_eventList->add($event);
440: }
441:
442: return $this;
443: }
444:
445: 446: 447: 448: 449: 450:
451: public function trackEvents($enabled)
452: {
453: $this->_trackEvents = (bool)$enabled;
454:
455: return $this;
456: }
457:
458: 459: 460: 461: 462:
463: public function isTrackingEvents()
464: {
465: return $this->_trackEvents && $this->_eventList;
466: }
467:
468: 469: 470: 471: 472: 473:
474: public function setEventList(EventList $eventList)
475: {
476: $this->_eventList = $eventList;
477: $this->_trackEvents = true;
478:
479: return $this;
480: }
481:
482: 483: 484: 485: 486:
487: public function unsetEventList()
488: {
489: $this->_eventList = null;
490: $this->_trackEvents = false;
491:
492: return $this;
493: }
494:
495: 496: 497: 498: 499:
500: public function __debugInfo()
501: {
502: $properties = get_object_vars($this);
503: $properties['_generalManager'] = '(object) EventManager';
504: $properties['_listeners'] = [];
505: foreach ($this->_listeners as $key => $priorities) {
506: $listenerCount = 0;
507: foreach ($priorities as $listeners) {
508: $listenerCount += count($listeners);
509: }
510: $properties['_listeners'][$key] = $listenerCount . ' listener(s)';
511: }
512: if ($this->_eventList) {
513: $count = count($this->_eventList);
514: for ($i = 0; $i < $count; $i++) {
515: $event = $this->_eventList[$i];
516: $subject = $event->getSubject();
517: $properties['_dispatchedEvents'][] = $event->getName() . ' with ' .
518: (is_object($subject) ? 'subject ' . get_class($subject) : 'no subject');
519: }
520: } else {
521: $properties['_dispatchedEvents'] = null;
522: }
523: unset($properties['_eventList']);
524:
525: return $properties;
526: }
527: }
528: