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

EventSettings::getParentEvents()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 9
cts 9
cp 1
rs 9.3222
c 0
b 0
f 0
cc 5
nc 7
nop 1
crap 5
1
<?php namespace Limoncello\Events\Package;
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 Limoncello\Contracts\Settings\Packages\EventSettingsInterface;
20
use Limoncello\Core\Reflection\ClassIsTrait;
21
use Limoncello\Events\Contracts\EventHandlerInterface;
22
use Limoncello\Events\Contracts\EventInterface;
23
use Limoncello\Events\SimpleEventEmitter;
24
use ReflectionClass;
25
use ReflectionException;
26
use ReflectionMethod;
27
28
/**
29
 * @package Limoncello\Events
30
 */
31
abstract class EventSettings implements EventSettingsInterface
32
{
33
    use ClassIsTrait;
34
35
    /**
36
     * @param array $appConfig
37
     *
38
     * @return array
39
     *
40
     * @throws ReflectionException
41
     */
42 2
    final public function get(array $appConfig): array
43
    {
44 2
        assert($appConfig !== null);
45
46 2
        $defaults = $this->getSettings();
47
48 2
        $eventsFolder = $defaults[static::KEY_EVENTS_FOLDER] ?? null;
49 2
        assert(
50 2
            $eventsFolder !== null && empty(glob($eventsFolder)) === false,
51 2
            "Invalid Events folder `$eventsFolder`."
52
        );
53
54 2
        $eventsFileMask = $defaults[static::KEY_EVENTS_FILE_MASK] ?? null;
55 2
        assert(empty($eventsFileMask) === false, "Invalid Events file mask `$eventsFileMask`.");
56
57 2
        $subscribersFolder = $defaults[static::KEY_SUBSCRIBERS_FOLDER] ?? null;
58 2
        assert(
59 2
            $subscribersFolder !== null && empty(glob($subscribersFolder)) === false,
60 2
            "Invalid Subscribers folder `$subscribersFolder`."
61
        );
62
63 2
        $subscribersFileMask = $defaults[static::KEY_SUBSCRIBERS_FILE_MASK] ?? null;
64 2
        assert(empty($subscribersFileMask) === false, "Invalid Subscribers file mask `$subscribersFileMask`.");
65
66 2
        $eventsPath      = $eventsFolder . DIRECTORY_SEPARATOR . $eventsFileMask;
67 2
        $subscribersPath = $subscribersFolder . DIRECTORY_SEPARATOR . $subscribersFileMask;
68
69 2
        $emitter      = new SimpleEventEmitter();
70 2
        $eventClasses = iterator_to_array(
71 2
            $this->selectClasses($eventsPath, EventInterface::class)
72
        );
73 2
        $handlerClasses = iterator_to_array(
74 2
            $this->selectClasses($subscribersPath, EventHandlerInterface::class)
75
        );
76
77 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...
78 2
        $emitter->addAllowedEvents($allowedEvents);
79
80 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...
81 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...
82
        }
83
84 2
        $cacheData = $emitter->getData();
85
86 2
        return $defaults + [static::KEY_CACHED_DATA => $cacheData];
87
    }
88
89
    /**
90
     * @return array
91
     */
92 2
    protected function getSettings(): array
93
    {
94
        return [
95 2
            static::KEY_EVENTS_FILE_MASK      => '*.php',
96 2
            static::KEY_SUBSCRIBERS_FILE_MASK => '*.php',
97
        ];
98
    }
99
100
    /**
101
     * @param iterable $eventClasses
102
     * @param iterable $handlerClasses
103
     *
104
     * @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...
105
     *
106
     * @throws ReflectionException
107
     */
108 2
    private function getEventSubscribers(iterable $eventClasses, iterable $handlerClasses): iterable
109
    {
110 2
        foreach ($handlerClasses as $handlerClass) {
111 2
            foreach ($this->selectEvenHandlers($handlerClass) as $eventClass => $subscriber) {
112 2
                foreach ($this->getChildEvents($eventClass, $eventClasses) as $childEventClass) {
113 2
                    yield $childEventClass => $subscriber;
114
                }
115
            }
116
        }
117
    }
118
119
    /**
120
     * @param string $handlerClass
121
     *
122
     * @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...
123
     *
124
     * @throws ReflectionException
125
     */
126 2
    private function selectEvenHandlers(string $handlerClass): iterable
127
    {
128 2
        $reflection = new ReflectionClass($handlerClass);
129 2
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC) as $method) {
130 2
            if ($this->isEventHandlerMethod($method) === true) {
131 2
                $eventClass = $method->getParameters()[0]->getClass()->getName();
132 2
                $subscriber = [$handlerClass, $method->getName()];
133 2
                yield $eventClass => $subscriber;
134
            }
135
        }
136
    }
137
138
    /**
139
     * @param string   $eventClass
140
     * @param iterable $eventClasses
141
     *
142
     * @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...
143
     *
144
     * @throws ReflectionException
145
     */
146 2
    private function getChildEvents(string $eventClass, iterable $eventClasses): iterable
147
    {
148 2
        $reflection = new ReflectionClass($eventClass);
149 2
        foreach ($eventClasses as $curEventClass) {
150 2
            $curReflection = new ReflectionClass($curEventClass);
151 2
            if ($curReflection->isAbstract() === false) {
152 2
                if ($eventClass === $curEventClass ||
153 2
                    $curReflection->isSubclassOf($eventClass) === true ||
154 2
                    ($reflection->isInterface() === true && $curReflection->implementsInterface($eventClass))
155
                ) {
156 2
                    yield $curEventClass;
157
                }
158
            }
159
        }
160
    }
161
162
    /**
163
     * @param iterable $eventClasses
164
     *
165
     * @return array
166
     */
167 2
    private function getParentEvents(iterable $eventClasses): array
168
    {
169 2
        $matches = [];
170
171 2
        foreach ($eventClasses as $eventClass) {
172
            // class itself
173 2
            if ($this->doesImplementEventInterface($eventClass) === true) {
174 2
                $matches[$eventClass] = true;
175
            }
176
            // parent classes or interfaces
177 2
            foreach (class_implements($eventClass, true) as $classOrInterface) {
178 2
                if ($this->doesImplementEventInterface($classOrInterface) === true) {
179 2
                    $matches[$classOrInterface] = true;
180
                }
181
            }
182
        }
183
184 2
        return array_keys($matches);
185
    }
186
187
    /**
188
     * @param ReflectionMethod $method
189
     *
190
     * @return bool
191
     */
192 2
    private function isEventHandlerMethod(ReflectionMethod $method): bool
193
    {
194
        $result =
195 2
            $method->isPublic() === true &&
196 2
            $method->isStatic() === true &&
197 2
            count($params = $method->getParameters()) === 1 &&
198 2
            $params[0]->getClass()->implementsInterface(EventInterface::class) === true;
199
200 2
        return $result;
201
    }
202
203
    /**
204
     * @param string $classOrInterface
205
     *
206
     * @return bool
207
     */
208 2
    private function doesImplementEventInterface(string $classOrInterface): bool
209
    {
210 2
        $isClass     = class_exists($classOrInterface, true);
211 2
        $isInterface = interface_exists($classOrInterface, true);
212 2
        assert($isClass xor $isInterface);
213
214 2
        $isEvent = in_array(EventInterface::class, class_implements($classOrInterface));
215
216 2
        return $isEvent;
217
    }
218
}
219