Passed
Push — master ( ce8388...8eb23a )
by Anton
02:13
created

EventEmitterStatic::propagateEvent()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 13
nop 2
dl 0
loc 32
rs 5.3846
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * @Author : a.zinovyev
6
 * @Package: emittr
7
 * @License: http://www.opensource.org/licenses/mit-license.php
8
 */
9
10
namespace xobotyi\emittr;
11
12
/**
13
 * Class EventEmitterStatic
14
 *
15
 * @method static void      emit(string $eventName, $payload = null)
16
 * @method static int       getMaxListeners()
17
 * @method static void      setMaxListeners(int $listenersCount)
18
 * @method static array     getEventNames()
19
 * @method static array     getListeners(?string $eventName = null)
20
 * @method static void      on(string $eventName, callable $callback)
21
 * @method static void      once(string $eventName, callable $callback)
22
 * @method static void      prependListener(string $eventName, callable $callback)
23
 * @method static void      prependOnceListener(string $eventName, callable $callback)
24
 * @method static void      removeAllListeners(?string $eventName = null)
25
 * @method static void      removeListener(string $eventName, callable $callback)
26
 *
27
 * @package xobotyi\emittr
28
 */
29
class EventEmitterStatic
30
{
31
    public const EVENT_LISTENER_ADDED   = 'listenerAdded';
32
    public const EVENT_LISTENER_REMOVED = 'listenerRemoved';
33
34
    protected static $staticListeners    = [];
35
    protected static $staticMaxListeners = [];
36
37
    public static function __callStatic($name, $arguments) {
38
        $calledClass = get_called_class();
39
        if (method_exists($calledClass, '_' . $name . 'Static')) {
40
            return forward_static_call_array([$calledClass, '_' . $name . 'Static'], $arguments);
41
        }
42
43
        throw new \Error('Call to undefined method ' . $calledClass . '::' . $name . '()');
44
    }
45
46
    private static function _emitStatic(string $eventName, $payload = null) :void {
47
        $calledClass = get_called_class();
48
49
        $event = new Event($eventName, $payload, $calledClass);
50
        if (!(self::$staticListeners[$calledClass] ?? false) || self::propagateEvent($event, self::$staticListeners[$calledClass])) {
51
            EventEmitterGlobal::propagateClassEvent($event);
52
        }
53
    }
54
55
    protected static function propagateEvent(Event $evt, array &$eventsListeners) :bool {
56
        $listeners = &$eventsListeners[$evt->getEventName()] ?? false;
57
58
        if (!$listeners) {
59
            return true;
60
        }
61
62
        $res = true;
63
64
        foreach ($listeners as $key => &$listener) {
65
            if (in_array($evt->getEventName(), [self::EVENT_LISTENER_ADDED, self::EVENT_LISTENER_REMOVED,])
66
                && $listener[1] === $evt->getPayload()['callback']) {
67
                continue;
68
            }
69
70
            call_user_func($listener[1], $evt);
71
72
            if ($listener[0]) {
73
                unset($listeners[$key]);
74
            }
75
76
            if (!$evt->isPropagatable()) {
77
                $res = false;
78
                break;
79
            }
80
        }
81
82
        if (empty($listeners)) {
83
            unset($listeners);
84
        }
85
86
        return $res;
87
    }
88
89
    private static function _getEventNamesStatic() :array {
90
        return \array_keys(self::$staticListeners[get_called_class()] ?? []);
91
    }
92
93
    private static function _getListenersStatic(?string $eventName = null) :array {
94
        return $eventName ? self::$staticListeners[get_called_class()][$eventName] ?? [] : self::$staticListeners[get_called_class()];
95
    }
96
97
    private static function _onStatic(string $eventName, callable $callback) :void {
98
        $calledClass = get_called_class();
99
100
        if (!isset(self::$staticListeners[$calledClass])) {
101
            self::$staticListeners[$calledClass] = [];
102
        }
103
104
        self::storeCallback(self::$staticListeners[$calledClass], $eventName, $callback, false, false, self::_getMaxListenersStatic());
105
106
        self::emit(self::EVENT_LISTENER_ADDED, ['eventName' => $eventName, 'callback' => $callback, 'once' => false]);
107
    }
108
109
    private static function _onceStatic(string $eventName, $callback) :void {
110
        $calledClass = get_called_class();
111
112
        if (!isset(self::$staticListeners[$calledClass])) {
113
            self::$staticListeners[$calledClass] = [];
114
        }
115
116
        self::storeCallback(self::$staticListeners[$calledClass], $eventName, $callback, true, false, self::_getMaxListenersStatic());
117
118
        self::emit(self::EVENT_LISTENER_ADDED, ['eventName' => $eventName, 'callback' => $callback, 'once' => true]);
119
    }
120
121
    private static function _prependListenerStatic(string $eventName, $callback) :void {
122
        $calledClass = get_called_class();
123
124
        if (!isset(self::$staticListeners[$calledClass])) {
125
            self::$staticListeners[$calledClass] = [];
126
        }
127
128
        self::storeCallback(self::$staticListeners[$calledClass], $eventName, $callback, false, true, self::_getMaxListenersStatic());
129
130
        self::emit(self::EVENT_LISTENER_ADDED, ['eventName' => $eventName, 'callback' => $callback, 'once' => false]);
131
    }
132
133
    private static function _prependOnceListenerStatic(string $eventName, $callback) :void {
134
        $calledClass = get_called_class();
135
136
        if (!isset(self::$staticListeners[$calledClass])) {
137
            self::$staticListeners[$calledClass] = [];
138
        }
139
140
        self::storeCallback(self::$staticListeners[$calledClass], $eventName, $callback, true, true, self::_getMaxListenersStatic());
141
142
        self::emit(self::EVENT_LISTENER_ADDED, ['eventName' => $eventName, 'callback' => $callback, 'once' => true]);
143
    }
144
145
    private static function _removeAllListenersStatic(?string $eventName = null) :void {
146
        $calledClass = get_called_class();
147
148
        if (!(self::$staticListeners[$calledClass] ?? false)) {
149
            return;
150
        }
151
152
        if ($eventName) {
153
            if (!(self::$staticListeners[$calledClass][$eventName] ?? false)) {
154
                return;
155
            }
156
157
            foreach (self::$staticListeners[$calledClass][$eventName] as &$callback) {
158
                self::emit(self::EVENT_LISTENER_REMOVED, ['eventName' => $eventName, 'callback' => &$callback[1]]);
159
            }
160
161
            unset(self::$staticListeners[$calledClass][$eventName]);
162
            self::$staticListeners[$calledClass] = array_filter(self::$staticListeners[$calledClass], function ($item) { return !empty($item); });
163
164
            return;
165
        }
166
167
        foreach (self::$staticListeners[$calledClass] as $eventName => &$listeners) {
168
            if (!$listeners) {
169
                continue;
170
            }
171
172
            foreach ($listeners as &$callback) {
173
                self::emit(self::EVENT_LISTENER_REMOVED, ['eventName' => $eventName, 'callback' => &$callback[1]]);
174
            }
175
        }
176
177
        self::$staticListeners[$calledClass] = [];
178
    }
179
180
    private static function _removeListenerStatic(string $eventName, callable $callback) :void {
181
        $calledClass = get_called_class();
182
        if (!(self::$staticListeners[$calledClass][$eventName] ?? false)) {
183
            return;
184
        }
185
186
        self::$staticListeners[$calledClass][$eventName] = array_filter(self::$staticListeners[$calledClass][$eventName],
187
            function ($item) use (&$callback) { return $item[1] !== $callback; });
188
189
        if (empty(self::$staticListeners[$calledClass][$eventName])) {
190
            unset(self::$staticListeners[$calledClass][$eventName]);
191
            self::$staticListeners[$calledClass] = array_filter(self::$staticListeners[$calledClass], function ($item) { return !empty($item); });
192
        }
193
194
        self::emit(self::EVENT_LISTENER_REMOVED, ['eventName' => $eventName, 'callback' => &$callback]);
195
    }
196
197
    private static function _setMaxListenersStatic(int $listenersCount) :void {
198
        if ($listenersCount < 0) {
199
            throw new \InvalidArgumentException('Listeners count must be greater or equal 0, got ' . $listenersCount);
200
        }
201
202
        self::$staticMaxListeners[get_called_class()] = $listenersCount;
203
    }
204
205
    private static function _getMaxListenersStatic() :int {
206
        return self::$staticMaxListeners[get_called_class()] ?? 10;
207
    }
208
209
    protected static function isValidCallback($callback) :bool {
210
        return is_callable($callback) || (is_array($callback) && count($callback) === 2 && is_string($callback[0]) && is_string($callback[1]));
211
    }
212
213
    protected static function storeCallback(array &$arrayToStore, string $eventName, &$callback, bool $once = false, bool $prepend = false, ?int $maxListeners = null) :void {
214
        if (!self::isValidCallback($callback)) {
215
            throw new Exception\EventEmitter("Event callback has to be a callable or an array of two elements representing classname and method to call");
216
        }
217
218
        $maxListeners = $maxListeners === null ? self::_getMaxListenersStatic() : $maxListeners;
219
220
        if (($arrayToStore[$eventName] ?? false) && $maxListeners && count($arrayToStore[$eventName]) === $maxListeners) {
221
            throw new Exception\EventEmitter("Maximum amount of listeners reached for event " . $eventName);
222
        }
223
224
        if (!isset($arrayToStore[$eventName])) {
225
            $arrayToStore[$eventName] = [];
226
        }
227
228
        $prepend
229
            ? array_unshift($arrayToStore[$eventName], [$once, $callback])
230
            : $arrayToStore[$eventName][] = [$once, $callback];
231
    }
232
}