Completed
Push — master ( a02a05...4a3478 )
by Neomerx
05:30
created

SimpleEventEmitter::addAllowedEvents()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php namespace Limoncello\Events;
2
3
/**
4
 * Copyright 2015-2018 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Closure;
20
use Limoncello\Events\Contracts\EventDispatcherInterface;
21
use Limoncello\Events\Contracts\EventEmitterInterface;
22
use Limoncello\Events\Contracts\EventInterface;
23
use Limoncello\Events\Exceptions\EventNotFoundException;
24
use ReflectionException;
25
use ReflectionMethod;
26
27
/**
28
 * @package Limoncello\Events
29
 */
30
class SimpleEventEmitter implements EventEmitterInterface, EventDispatcherInterface
31
{
32
    /**
33
     * All events known to system with or without corresponding event handler.
34
     *
35
     * @var array
36
     */
37
    private $allowedEvents = [];
38
39
    /**
40
     * @var array
41
     */
42
    private $subscribers = [];
43
44
    /**
45
     * @var bool
46
     */
47
    private $cancellingEnabled = false;
48
49
    /**
50
     * @inheritdoc
51
     */
52 5
    public function emit(string $eventName, array $arguments = []): void
53
    {
54 5
        $this->isCancellingEnabled() === true ?
55 1
            $this->emitWithCancellingPropagationCheck($eventName, $arguments) :
56 5
            $this->emitWithoutCancellingPropagationCheck($eventName, $arguments);
57
    }
58
59
    /**
60
     * @inheritdoc
61
     */
62 1
    public function dispatch(EventInterface $event): void
63
    {
64 1
        $this->emit(get_class($event), [$event]);
65
    }
66
67
    /**
68
     * @param string $eventName
69
     *
70
     * @return SimpleEventEmitter
71
     */
72 5
    public function addAllowedEvent(string $eventName): self
73
    {
74 5
        $this->allowedEvents[$eventName] = true;
75
76 5
        return $this;
77
    }
78
79
    /**
80
     * @param string[] $eventNames
81
     *
82
     * @return SimpleEventEmitter
83
     */
84 2
    public function addAllowedEvents(array $eventNames): self
85
    {
86 2
        foreach ($eventNames as $eventName) {
87 2
            $this->addAllowedEvent($eventName);
88
        }
89
90 2
        return $this;
91
    }
92
93
    /**
94
     * @param string   $eventName
95
     * @param callable $subscriber
96
     *
97
     * @return self
98
     *
99
     * @SuppressWarnings(PHPMD.ElseExpression)
100
     */
101 5
    public function subscribe(string $eventName, callable $subscriber): self
102
    {
103 5
        if ($subscriber instanceof Closure || ($staticMethod = $this->parseStaticMethod($subscriber)) === null) {
104 3
            $this->subscribers[$eventName][] = $subscriber;
105
        } else {
106 5
            assert($staticMethod !== null);
107 5
            $this->subscribers[$eventName][] = $this->getUnifiedStaticMethodRepresentation($staticMethod);
108
        }
109
110 5
        $this->addAllowedEvent($eventName);
111
112 5
        return $this;
113
    }
114
115
    /**
116
     * @param string   $eventName
117
     * @param callable $subscriber
118
     *
119
     * @return self
120
     */
121 1
    public function unSubscribe(string $eventName, callable $subscriber): self
122
    {
123 1
        if (($subscriber instanceof Closure) === false &&
124 1
            ($staticMethod = $this->parseStaticMethod($subscriber)) !== null
125
        ) {
126 1
            $subscriber = $this->getUnifiedStaticMethodRepresentation($staticMethod);
127
        }
128
129 1
        $eventSubscribers = $this->getEventSubscribers($eventName);
130
        $eventSubscribers = array_filter($eventSubscribers, function ($curSubscriber) use ($subscriber) {
131 1
            return $curSubscriber !== $subscriber;
132 1
        });
133
134 1
        return $this->setEventSubscribers($eventName, $eventSubscribers);
135
    }
136
137
    /**
138
     * @return bool
139
     */
140 5
    public function isCancellingEnabled(): bool
141
    {
142 5
        return $this->cancellingEnabled;
143
    }
144
145
    /**
146
     * @return self
147
     */
148 2
    public function enableCancelling(): self
149
    {
150 2
        $this->cancellingEnabled = true;
151
152 2
        return $this;
153
    }
154
155
    /**
156
     * @return self
157
     */
158 3
    public function disableCancelling(): self
159
    {
160 3
        $this->cancellingEnabled = false;
161
162 3
        return $this;
163
    }
164
165
    /**
166
     * @param array $data
167
     *
168
     * @return self
0 ignored issues
show
Documentation introduced by
Should the return type not be \self?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
169
     */
170 3
    public function setData(array $data): self
171
    {
172 3
        assert(count($data) == 2);
173
174 3
        [$allowedEvents, $subscribers] = $data;
0 ignored issues
show
Bug introduced by
The variable $allowedEvents does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $subscribers does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
175
176 3
        assert($this->checkAllSubscribersAreStatic($subscribers) === true);
177
178 3
        return $this->setAllowedEvents($allowedEvents)->setSubscribers($subscribers);
179
    }
180
181
    /**
182
     * @return array
183
     */
184 4
    public function getData(): array
185
    {
186 4
        $subscribers = [];
187
188 4
        foreach ($this->getSubscribers() as $eventName => $subscribersList) {
189 4
            $eventSubscribers = [];
190 4
            foreach ($subscribersList as $subscriber) {
191 4
                if (($staticMethod = $this->parseStaticMethod($subscriber)) !== null) {
192 4
                    $eventSubscribers[] = $this->getUnifiedStaticMethodRepresentation($staticMethod);
193
                }
194
            }
195
196 4
            if (empty($eventSubscribers) === false) {
197 4
                $subscribers[$eventName] = $eventSubscribers;
198
            }
199
        }
200
201 4
        $data = [$this->getAllowedEvents(), $subscribers];
202
203 4
        return $data;
204
    }
205
206
    /**
207
     * @param string $eventName
208
     *
209
     * @return bool
210
     */
211 5
    protected function isEventAllowed(string $eventName): bool
212
    {
213 5
        return array_key_exists($eventName, $this->allowedEvents);
214
    }
215
216
    /**
217
     * @return array
218
     */
219 4
    protected function getAllowedEvents(): array
220
    {
221 4
        return $this->allowedEvents;
222
    }
223
224
    /**
225
     * @param array $allowedEvents
226
     *
227
     * @return self
228
     */
229 3
    protected function setAllowedEvents(array $allowedEvents): self
230
    {
231 3
        $this->allowedEvents = $allowedEvents;
232
233 3
        return $this;
234
    }
235
236
    /**
237
     * @return array
238
     */
239 5
    protected function getSubscribers(): array
240
    {
241 5
        return $this->subscribers;
242
    }
243
244
    /**
245
     * @param callable[] $subscribers
246
     *
247
     * @return self
248
     */
249 3
    protected function setSubscribers(array $subscribers): self
250
    {
251 3
        $this->subscribers = $subscribers;
252
253 3
        return $this;
254
    }
255
256
    /**
257
     * @param string $eventName
258
     * @param array  $arguments
259
     *
260
     * @return void
261
     */
262 5
    protected function emitWithoutCancellingPropagationCheck(string $eventName, array $arguments = []): void
263
    {
264 5
        foreach ($this->getEventSubscribers($eventName) as $subscriber) {
265 4
            call_user_func_array($subscriber, $arguments);
266
        }
267
    }
268
269
    /**
270
     * @param string $eventName
271
     * @param array  $arguments
272
     *
273
     * @return void
274
     */
275 1
    protected function emitWithCancellingPropagationCheck(string $eventName, array $arguments = []): void
276
    {
277 1
        foreach ($this->getEventSubscribers($eventName) as $subscriber) {
278 1
            if (call_user_func_array($subscriber, $arguments) === false) {
279 1
                break;
280
            }
281
        }
282
    }
283
284
    /**
285
     * @param string $eventName
286
     *
287
     * @return array
288
     */
289 5
    private function getEventSubscribers(string $eventName): array
290
    {
291 5
        if ($this->isEventAllowed($eventName) === false) {
292 1
            throw new EventNotFoundException($eventName);
293
        }
294
295 4
        $result = $this->getSubscribers()[$eventName] ?? [];
296
297 4
        return $result;
298
    }
299
300
    /**
301
     * @param string     $eventName
302
     * @param callable[] $eventSubscribers
303
     *
304
     * @return self
305
     */
306 1
    private function setEventSubscribers(string $eventName, array $eventSubscribers): self
307
    {
308 1
        $this->subscribers[$eventName] = $eventSubscribers;
309
310 1
        return $this;
311
    }
312
313
    /**
314
     * This debugging function checks subscribers are
315
     * [
316
     *     ...
317
     *     'string_event_name' => [static callable, static callable, ...],
318
     *     ...
319
     * ]
320
     *
321
     * @param array $subscribers
322
     *
323
     * @return bool
324
     */
325 4
    private function checkAllSubscribersAreStatic(array $subscribers): bool
326
    {
327 4
        $result = true;
328 4
        foreach ($subscribers as $eventName => $callableList) {
329 4
            if (is_string($eventName) === false || is_array($callableList) === false) {
330 1
                $result = false;
331 1
                break;
332
            }
333 4
            foreach ($callableList as $mightBeCallable) {
334 4
                $method = $this->parseStaticMethod($mightBeCallable);
335 4
                if ($method === null || $method->isStatic() === false) {
336 1
                    $result = false;
337 4
                    break;
338
                }
339
            }
340
        }
341
342 4
        return $result;
343
    }
344
345
    /**
346
     * @param $mightBeCallable
347
     *
348
     * @return null|ReflectionMethod
349
     *
350
     * @SuppressWarnings(PHPMD.ElseExpression)
351
     */
352 6
    private function parseStaticMethod($mightBeCallable): ?ReflectionMethod
353
    {
354
        // static callable could be in form of 'ClassName::methodName' or ['ClassName', 'methodName']
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
355 6
        if (is_string($mightBeCallable) === true &&
356 6
            count($mightBeCallablePair = explode('::', $mightBeCallable, 2)) === 2
357
        ) {
358 1
            list ($mightBeClassName, $mightBeMethodName) = $mightBeCallablePair;
359 6
        } elseif (is_array($mightBeCallable) === true && count($mightBeCallable) === 2) {
360 6
            list ($mightBeClassName, $mightBeMethodName) = $mightBeCallable;
361
        } else {
362 1
            return null;
363
        }
364
365
        try {
366 6
            $reflectionMethod = new ReflectionMethod($mightBeClassName, $mightBeMethodName);
367 1
        } catch (ReflectionException $exception) {
368 1
            return null;
369
        }
370
371 5
        if ($reflectionMethod->isStatic() === false) {
372 3
            return null;
373
        }
374
375 5
        return $reflectionMethod;
376
    }
377
378
    /**
379
     * @param ReflectionMethod $staticMethod
380
     *
381
     * @return callable
382
     */
383 5
    private function getUnifiedStaticMethodRepresentation(ReflectionMethod $staticMethod): callable
384
    {
385 5
        return [$staticMethod->class, $staticMethod->name];
386
    }
387
}
388