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