EventSettings   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 2
dl 0
loc 190
ccs 73
cts 73
cp 1
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 48 4
A getSettings() 0 7 1
A getEventSubscribers() 0 10 4
A selectEvenHandlers() 0 11 3
B getChildEvents() 0 15 7
A getParentEvents() 0 19 5
A isEventHandlerMethod() 0 10 4
A doesImplementEventInterface() 0 10 1
1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Events\Package;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Contracts\Settings\Packages\EventSettingsInterface;
22
use Limoncello\Common\Reflection\ClassIsTrait;
23
use Limoncello\Events\Contracts\EventHandlerInterface;
24
use Limoncello\Events\Contracts\EventInterface;
25
use Limoncello\Events\SimpleEventEmitter;
26
use ReflectionClass;
27
use ReflectionException;
28
use ReflectionMethod;
29
use function assert;
30
use function class_exists;
31
use function class_implements;
32
use function count;
33
use function glob;
34
use function in_array;
35
use function interface_exists;
36
use function iterator_to_array;
37
38
/**
39
 * @package Limoncello\Events
40
 */
41
abstract class EventSettings implements EventSettingsInterface
42
{
43
    use ClassIsTrait;
44
45
    /**
46
     * @param array $appConfig
47
     *
48
     * @return array
49
     *
50
     * @throws ReflectionException
51
     */
52 2
    final public function get(array $appConfig): array
53
    {
54 2
        assert($appConfig !== null);
55
56 2
        $defaults = $this->getSettings();
57
58 2
        $eventsFolder = $defaults[static::KEY_EVENTS_FOLDER] ?? null;
59 2
        assert(
60 2
            $eventsFolder !== null && empty(glob($eventsFolder)) === false,
61 2
            "Invalid Events folder `$eventsFolder`."
62
        );
63
64 2
        $eventsFileMask = $defaults[static::KEY_EVENTS_FILE_MASK] ?? null;
65 2
        assert(empty($eventsFileMask) === false, "Invalid Events file mask `$eventsFileMask`.");
66
67 2
        $subscribersFolder = $defaults[static::KEY_SUBSCRIBERS_FOLDER] ?? null;
68 2
        assert(
69 2
            $subscribersFolder !== null && empty(glob($subscribersFolder)) === false,
70 2
            "Invalid Subscribers folder `$subscribersFolder`."
71
        );
72
73 2
        $subscribersFileMask = $defaults[static::KEY_SUBSCRIBERS_FILE_MASK] ?? null;
74 2
        assert(empty($subscribersFileMask) === false, "Invalid Subscribers file mask `$subscribersFileMask`.");
75
76 2
        $eventsPath      = $eventsFolder . DIRECTORY_SEPARATOR . $eventsFileMask;
77 2
        $subscribersPath = $subscribersFolder . DIRECTORY_SEPARATOR . $subscribersFileMask;
78
79 2
        $emitter      = new SimpleEventEmitter();
80
        /** @noinspection PhpParamsInspection */
81 2
        $eventClasses = iterator_to_array(
82 2
            $this->selectClasses($eventsPath, EventInterface::class)
83
        );
84
        /** @noinspection PhpParamsInspection */
85 2
        $handlerClasses = iterator_to_array(
86 2
            $this->selectClasses($subscribersPath, EventHandlerInterface::class)
87
        );
88
89 2
        $allowedEvents = $this->getParentEvents($eventClasses);
0 ignored issues
show
Documentation introduced by
$eventClasses is of type array, but the function expects a object<Limoncello\Events\Package\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
90 2
        $emitter->addAllowedEvents($allowedEvents);
91
92 2
        foreach ($this->getEventSubscribers($eventClasses, $handlerClasses) as $eventClass => $subscriber) {
0 ignored issues
show
Documentation introduced by
$eventClasses is of type array, but the function expects a object<Limoncello\Events\Package\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$handlerClasses is of type array, but the function expects a object<Limoncello\Events\Package\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
93 2
            $emitter->subscribe($eventClass, $subscriber);
0 ignored issues
show
Documentation introduced by
$subscriber is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94
        }
95
96 2
        $cacheData = $emitter->getData();
97
98 2
        return $defaults + [static::KEY_CACHED_DATA => $cacheData];
99
    }
100
101
    /**
102
     * @return array
103
     */
104 2
    protected function getSettings(): array
105
    {
106
        return [
107 2
            static::KEY_EVENTS_FILE_MASK      => '*.php',
108 2
            static::KEY_SUBSCRIBERS_FILE_MASK => '*.php',
109
        ];
110
    }
111
112
    /**
113
     * @param iterable $eventClasses
114
     * @param iterable $handlerClasses
115
     *
116
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be \Generator?

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...
117
     *
118
     * @throws ReflectionException
119
     */
120 2
    private function getEventSubscribers(iterable $eventClasses, iterable $handlerClasses): iterable
121
    {
122 2
        foreach ($handlerClasses as $handlerClass) {
123 2
            foreach ($this->selectEvenHandlers($handlerClass) as $eventClass => $subscriber) {
124 2
                foreach ($this->getChildEvents($eventClass, $eventClasses) as $childEventClass) {
125 2
                    yield $childEventClass => $subscriber;
126
                }
127
            }
128
        }
129
    }
130
131
    /**
132
     * @param string $handlerClass
133
     *
134
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be \Generator?

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...
135
     *
136
     * @throws ReflectionException
137
     */
138 2
    private function selectEvenHandlers(string $handlerClass): iterable
139
    {
140 2
        $reflection = new ReflectionClass($handlerClass);
141 2
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC) as $method) {
142 2
            if ($this->isEventHandlerMethod($method) === true) {
143 2
                $eventClass = $method->getParameters()[0]->getClass()->getName();
144 2
                $subscriber = [$handlerClass, $method->getName()];
145 2
                yield $eventClass => $subscriber;
146
            }
147
        }
148
    }
149
150
    /**
151
     * @param string   $eventClass
152
     * @param iterable $eventClasses
153
     *
154
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be \Generator?

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...
155
     *
156
     * @throws ReflectionException
157
     */
158 2
    private function getChildEvents(string $eventClass, iterable $eventClasses): iterable
159
    {
160 2
        $reflection = new ReflectionClass($eventClass);
161 2
        foreach ($eventClasses as $curEventClass) {
162 2
            $curReflection = new ReflectionClass($curEventClass);
163 2
            if ($curReflection->isAbstract() === false) {
164 2
                if ($eventClass === $curEventClass ||
165 2
                    $curReflection->isSubclassOf($eventClass) === true ||
166 2
                    ($reflection->isInterface() === true && $curReflection->implementsInterface($eventClass))
167
                ) {
168 2
                    yield $curEventClass;
169
                }
170
            }
171
        }
172
    }
173
174
    /**
175
     * @param iterable $eventClasses
176
     *
177
     * @return array
178
     */
179 2
    private function getParentEvents(iterable $eventClasses): array
180
    {
181 2
        $matches = [];
182
183 2
        foreach ($eventClasses as $eventClass) {
184
            // class itself
185 2
            if ($this->doesImplementEventInterface($eventClass) === true) {
186 2
                $matches[$eventClass] = true;
187
            }
188
            // parent classes or interfaces
189 2
            foreach (class_implements($eventClass, true) as $classOrInterface) {
190 2
                if ($this->doesImplementEventInterface($classOrInterface) === true) {
191 2
                    $matches[$classOrInterface] = true;
192
                }
193
            }
194
        }
195
196 2
        return array_keys($matches);
197
    }
198
199
    /**
200
     * @param ReflectionMethod $method
201
     *
202
     * @return bool
203
     */
204 2
    private function isEventHandlerMethod(ReflectionMethod $method): bool
205
    {
206
        $result =
207 2
            $method->isPublic() === true &&
208 2
            $method->isStatic() === true &&
209 2
            count($params = $method->getParameters()) === 1 &&
210 2
            $params[0]->getClass()->implementsInterface(EventInterface::class) === true;
211
212 2
        return $result;
213
    }
214
215
    /**
216
     * @param string $classOrInterface
217
     *
218
     * @return bool
219
     */
220 2
    private function doesImplementEventInterface(string $classOrInterface): bool
221
    {
222 2
        $isClass     = class_exists($classOrInterface, true);
223 2
        $isInterface = interface_exists($classOrInterface, true);
224 2
        assert($isClass xor $isInterface);
225
226 2
        $isEvent = in_array(EventInterface::class, class_implements($classOrInterface));
227
228 2
        return $isEvent;
229
    }
230
}
231