GlobalEventHandler   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 62
eloc 99
dl 0
loc 359
c 0
b 0
f 0
rs 3.44

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getMaxListenersCount() 0 2 1
A getEventNames() 0 2 2
A getInstance() 0 6 2
A propagateEventGlobal() 0 10 3
A removeAllListeners() 0 28 6
A getListeners() 0 2 3
A setMaxListenersCount() 0 8 2
B storeCallback() 0 16 7
A prependListener() 0 8 2
A prependOnceListener() 0 8 2
A off() 0 16 4
A isValidCallback() 0 2 6
B propagateEvent() 0 33 9
A on() 0 8 2
A once() 0 8 2
B loadListeners() 0 27 9

How to fix   Complexity   

Complex Class

Complex classes like GlobalEventHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GlobalEventHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @Author : a.zinovyev
4
 * @Package: emittr
5
 * @License: http://www.opensource.org/licenses/mit-license.php
6
 */
7
8
namespace xobotyi\emittr;
9
10
final class GlobalEventHandler implements Interfaces\GlobalEventHandler
11
{
12
    /**
13
     * @var \xobotyi\emittr\GlobalEventHandler;
14
     */
15
    private static $instance;
16
17
    private $listeners;
18
19
    /**
20
     * @var int
21
     */
22
    private $maxListenersCount = 10;
23
24
    /**
25
     * @inheritdoc
26
     *
27
     * @return \xobotyi\emittr\GlobalEventHandler
28
     */
29
    public static function getInstance() :self {
30
        if (!self::$instance) {
31
            self::$instance = new self();
32
        }
33
34
        return self::$instance;
35
    }
36
37
    /**
38
     * @inheritdoc
39
     *
40
     * @param \xobotyi\emittr\Event $event
41
     * @param array                 $eventsListeners
42
     *
43
     * @return bool
44
     */
45
    public static function propagateEvent(Event $event, array &$eventsListeners) :bool {
46
        $eventName = $event->getEventName();
47
48
        if (empty($eventsListeners[$eventName])) {
49
            return true;
50
        }
51
52
        $listeners = &$eventsListeners[$eventName];
53
        $result    = true;
54
55
        foreach ($listeners as $key => &$listener) {
56
            if (in_array($eventName, [EventEmitter::EVENT_LISTENER_ADDED, EventEmitter::EVENT_LISTENER_REMOVED,]) &&
57
                $event->getPayload()['callback'] && $event->getPayload()['callback'] === $listener[1]) {
58
                continue;
59
            }
60
61
            call_user_func($listener[1], $event);
62
63
            if ($listener[0]) {
64
                unset($listeners[$key]);
65
            }
66
67
            if (!$event->isPropagatable()) {
68
                $result = false;
69
                break;
70
            }
71
        }
72
73
        if (empty($listeners)) {
74
            unset($listeners);
75
        }
76
77
        return $result;
78
    }
79
80
    /**
81
     * @inheritdoc
82
     *
83
     * @param callable|array|string $callback
84
     *
85
     * @return bool
86
     */
87
    public static function isValidCallback($callback) :bool {
88
        return is_string($callback) || is_callable($callback) || (is_array($callback) && count($callback) === 2 && is_string($callback[0]) && is_string($callback[1]));
89
    }
90
91
    /**
92
     * @inheritdoc
93
     *
94
     * @param array                 $arrayToStore
95
     * @param string                $eventName
96
     * @param callable|array|string $callback
97
     * @param int                   $maxListeners
98
     * @param bool                  $once
99
     * @param bool                  $prepend
100
     *
101
     * @throws \xobotyi\emittr\Exception\EventEmitter
102
     */
103
    public static function storeCallback(array &$arrayToStore, string $eventName, $callback, int $maxListeners = 10, bool $once = false, bool $prepend = false) :void {
104
        if (!self::isValidCallback($callback)) {
105
            throw new Exception\EventEmitter("Event callback has to be a callable or an array of two elements representing classname and method to call");
106
        }
107
108
        if (!empty($arrayToStore[$eventName]) && $maxListeners && count($arrayToStore[$eventName]) >= $maxListeners) {
109
            throw new Exception\EventEmitter("Maximum amount of listeners reached for event " . $eventName);
110
        }
111
112
        if (empty($arrayToStore[$eventName])) {
113
            $arrayToStore[$eventName] = [];
114
        }
115
116
        $prepend
117
            ? array_unshift($arrayToStore[$eventName], [$once, $callback])
118
            : $arrayToStore[$eventName][] = [$once, $callback];
119
    }
120
121
    /**
122
     * @inheritdoc
123
     *
124
     * @param array $listeners
125
     *
126
     * @return \xobotyi\emittr\GlobalEventHandler
127
     * @throws \xobotyi\emittr\Exception\EventEmitter
128
     */
129
    public function loadListeners(array $listeners) :self {
130
        foreach ($listeners as $className => &$classListeners) {
131
            foreach ($classListeners as $eventName => $eventListeners) {
132
                foreach ($eventListeners as $listener) {
133
                    if (!isset($this->listeners[$className][$eventName])) {
134
                        $this->listeners[$className][$eventName] = [];
135
                    }
136
137
                    if (isset($listener['once']) && isset($listener['callback'])) {
138
                        self::storeCallback($this->listeners[$className], $eventName, $listener['callback'], $this->maxListenersCount, $listener['once'], false);
139
                        continue;
140
                    }
141
142
                    self::storeCallback($this->listeners[$className], $eventName, $listener, $this->maxListenersCount, false, false);
143
                }
144
145
                if (empty($this->listeners[$className][$eventName])) {
146
                    unset($this->listeners[$className][$eventName]);
147
                }
148
            }
149
150
            if (empty($this->listeners[$className])) {
151
                unset($this->listeners[$className]);
152
            }
153
        }
154
155
        return $this;
156
    }
157
158
    /**
159
     * @inheritdoc
160
     *
161
     * @param string                $className
162
     * @param string                $eventName
163
     * @param callable|array|string $callback
164
     *
165
     * @return \xobotyi\emittr\GlobalEventHandler
166
     * @throws \xobotyi\emittr\Exception\EventEmitter
167
     */
168
    public function on(string $className, string $eventName, $callback) :self {
169
        if (!isset($this->listeners[$className][$eventName])) {
170
            $this->listeners[$className][$eventName] = [];
171
        }
172
173
        self::storeCallback($this->listeners[$className], $eventName, $callback, $this->maxListenersCount, false, false);
174
175
        return $this;
176
    }
177
178
    /**
179
     * @inheritdoc
180
     *
181
     * @param string                $className
182
     * @param string                $eventName
183
     * @param callable|array|string $callback
184
     *
185
     * @return \xobotyi\emittr\GlobalEventHandler
186
     * @throws \xobotyi\emittr\Exception\EventEmitter
187
     */
188
    public function once(string $className, string $eventName, $callback) :self {
189
        if (!isset($this->listeners[$className][$eventName])) {
190
            $this->listeners[$className][$eventName] = [];
191
        }
192
193
        self::storeCallback($this->listeners[$className], $eventName, $callback, $this->maxListenersCount, true, false);
194
195
        return $this;
196
    }
197
198
    /**
199
     * @inheritdoc
200
     *
201
     * @param string                $className
202
     * @param string                $eventName
203
     * @param callable|array|string $callback
204
     *
205
     * @return \xobotyi\emittr\GlobalEventHandler
206
     * @throws \xobotyi\emittr\Exception\EventEmitter
207
     */
208
    public function prependListener(string $className, string $eventName, $callback) :self {
209
        if (!isset($this->listeners[$className][$eventName])) {
210
            $this->listeners[$className][$eventName] = [];
211
        }
212
213
        self::storeCallback($this->listeners[$className], $eventName, $callback, $this->maxListenersCount, false, true);
214
215
        return $this;
216
    }
217
218
    /**
219
     * @inheritdoc
220
     *
221
     * @param string                $className
222
     * @param string                $eventName
223
     * @param callable|array|string $callback
224
     *
225
     * @return \xobotyi\emittr\GlobalEventHandler
226
     * @throws \xobotyi\emittr\Exception\EventEmitter
227
     */
228
    public function prependOnceListener(string $className, string $eventName, $callback) :self {
229
        if (!isset($this->listeners[$className][$eventName])) {
230
            $this->listeners[$className][$eventName] = [];
231
        }
232
233
        self::storeCallback($this->listeners[$className], $eventName, $callback, $this->maxListenersCount, true, true);
234
235
        return $this;
236
    }
237
238
    /**
239
     * @inheritdoc
240
     *
241
     * @param string                $className
242
     * @param string                $eventName
243
     * @param callable|array|string $callback
244
     *
245
     * @return \xobotyi\emittr\GlobalEventHandler
246
     */
247
    public function off(string $className, string $eventName, $callback) :self {
248
        if (empty($this->listeners[$className][$eventName])) {
249
            return $this;
250
        }
251
252
        $this->listeners[$className][$eventName] = \array_values(\array_filter($this->listeners[$className][$eventName], function ($item) use (&$callback) { return $item[1] !== $callback; }));
253
254
        if (empty($this->listeners[$className][$eventName])) {
255
            unset($this->listeners[$className][$eventName]);
256
        }
257
258
        if (empty($this->listeners[$className])) {
259
            unset($this->listeners[$className]);
260
        }
261
262
        return $this;
263
    }
264
265
    /**
266
     * @inheritdoc
267
     *
268
     * @param null|string $className
269
     * @param null|string $eventName
270
     *
271
     * @return \xobotyi\emittr\GlobalEventHandler
272
     */
273
    public function removeAllListeners(?string $className = null, ?string $eventName = null) :self {
274
        if ($className === null) {
275
            $this->listeners = [];
276
277
            return $this;
278
        }
279
280
        if (empty($this->listeners[$className])) {
281
            return $this;
282
        }
283
284
        if ($eventName === null) {
285
            unset($this->listeners[$className]);
286
287
            return $this;
288
        }
289
290
        if (empty($this->listeners[$className][$eventName])) {
291
            return $this;
292
        }
293
294
        unset($this->listeners[$className][$eventName]);
295
296
        if (empty($this->listeners[$className])) {
297
            unset($this->listeners[$className]);
298
        }
299
300
        return $this;
301
    }
302
303
    /**
304
     * @inheritdoc
305
     *
306
     * @param string $className
307
     *
308
     * @return array
309
     */
310
    public function getEventNames(string $className) :array {
311
        return empty($this->listeners[$className]) ? [] : \array_keys($this->listeners[$className]);
312
    }
313
314
    /**
315
     * @inheritdoc
316
     *
317
     * @param null|string $className
318
     * @param null|string $eventName
319
     *
320
     * @return array
321
     */
322
    public function getListeners(?string $className = null, ?string $eventName = null) :array {
323
        return $className ? $eventName ? $this->listeners[$className][$eventName] ?? [] : $this->listeners[$className] ?? [] : $this->listeners;
324
    }
325
326
    /**
327
     * @inheritdoc
328
     *
329
     * @return int
330
     */
331
    public function getMaxListenersCount() :int {
332
        return $this->maxListenersCount;
333
    }
334
335
    /**
336
     * @inheritdoc
337
     *
338
     * @param int $maxListenersCount
339
     *
340
     * @return \xobotyi\emittr\GlobalEventHandler
341
     */
342
    public function setMaxListenersCount(int $maxListenersCount) :self {
343
        if ($maxListenersCount < 0) {
344
            throw new \InvalidArgumentException('Listeners count must be greater or equal 0, got ' . $maxListenersCount);
345
        }
346
347
        $this->maxListenersCount = $maxListenersCount;
348
349
        return $this;
350
    }
351
352
    /**
353
     * @inheritdoc
354
     *
355
     * @param \xobotyi\emittr\Event $event
356
     *
357
     * @return bool
358
     */
359
    public function propagateEventGlobal(Event $event) :bool {
360
        if (substr($event->getSourceClass(), 0, 15) === 'class@anonymous') {
361
            return true;
362
        }
363
364
        if (empty($this->listeners[$event->getSourceClass()])) {
365
            return true;
366
        }
367
368
        return self::propagateEvent($event, $this->listeners[$event->getSourceClass()]);
369
    }
370
}