Test Failed
Push — master ( b83827...526624 )
by Divine Niiquaye
07:58
created

AnnotationLoader::findAnnotations()   C

Complexity

Conditions 13
Paths 24

Size

Total Lines 63
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 33
c 1
b 0
f 0
dl 0
loc 63
rs 6.6166
cc 13
nc 24
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.2 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Biurad\Annotations;
19
20
use FilesystemIterator;
21
use RecursiveDirectoryIterator;
22
use RecursiveIteratorIterator;
23
use ReflectionClass;
24
use ReflectionClassConstant;
25
use ReflectionMethod;
26
use ReflectionParameter;
27
use ReflectionProperty;
28
use Reflector;
29
use RegexIterator;
30
use Spiral\Attributes\ReaderInterface;
31
32
class AnnotationLoader implements LoaderInterface
33
{
34
    /** @var ReaderInterface */
35
    private $annotation;
36
37
    /** @var ListenerInterface[] */
38
    private $listeners;
39
40
    /** @var string[] */
41
    private $resources = [];
42
43
    /**
44
     * @param ReaderInterface $reader
45
     */
46
    public function __construct(ReaderInterface $reader)
47
    {
48
        $this->annotation = $reader;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function attachListener(ListenerInterface ...$listeners): void
55
    {
56
        foreach ($listeners as $listener) {
57
            $this->listeners[] = $listener;
58
        }
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function attach(string ...$resources): void
65
    {
66
        foreach ($resources as $resource) {
67
            $this->resources[] = $resource;
68
        }
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function load(): iterable
75
    {
76
        $annotations = [];
77
78
        foreach ($this->resources as $resource) {
79
            foreach ($this->listeners as $listener) {
80
                if (\class_exists($resource) || \is_dir($resource)) {
81
                    $annotations += $this->findAnnotations($resource, $listener);
82
83
                    continue;
84
                }
85
            }
86
87
            //TODO: Read annotations from functions ...
88
        }
89
90
        foreach ($this->listeners as $listener) {
91
            if (null !== $found = $listener->onAnnotation($annotations)) {
92
                yield $found;
93
            }
94
        }
95
    }
96
97
    /**
98
     * Finds annotations in the given resource
99
     *
100
     * @param string            $resource
101
     * @param ListenerInterface $listener
102
     *
103
     * @return array<string,array<string,mixed>>
104
     */
105
    private function findAnnotations(string $resource, ListenerInterface $listener): array
106
    {
107
        $classes = $annotations = [];
108
109
        if (\is_dir($resource)) {
110
            $classes = \array_merge($this->findClasses($resource), $classes);
111
        } elseif (\class_exists($resource)) {
112
            $classes[] = $resource;
113
        }
114
115
        /** @var class-string $class */
116
        foreach ($classes as $class) {
117
            $classReflection = new ReflectionClass($class);
118
            $className       = $classReflection->getName();
119
120
            if ($classReflection->isAbstract()) {
121
                throw new InvalidAnnotationException(\sprintf(
122
                    'Annotations from class "%s" cannot be read as it is abstract.',
123
                    $classReflection->getName()
124
                ));
125
            }
126
127
            foreach ($this->getAnnotations($classReflection, $listener) as $annotation) {
128
                $annotations[$className]['class'] = $annotation;
129
            }
130
131
            // Reflections belonging to class object.
132
            $reflections = \array_merge(
133
                $classReflection->getMethods(),
134
                $classReflection->getProperties(),
135
                $classReflection->getConstants()
136
            );
137
138
            foreach ($reflections as $reflection) {
139
                if ($reflection instanceof ReflectionMethod && $reflection->isAbstract()) {
140
                    continue;
141
                }
142
143
                foreach ($this->getAnnotations($reflection, $listener) as $annotation) {
144
                    if ($reflection instanceof ReflectionMethod) {
145
                        $annotations[$className]['method'][] = [$reflection, $annotation];
146
147
                        foreach ($this->getMethodParameter($reflection->getParameters(), $listener) as $parameter) {
148
                            $annotations[$className]['method_property'][] = $parameter;
149
                        }
150
151
                        continue;
152
                    }
153
154
                    if ($reflection instanceof ReflectionClassConstant) {
155
                        $annotations[$className]['constant'][] = [$reflection, $annotation];
156
157
                        continue;
158
                    }
159
160
                    $annotations[$className]['property'][] = [$reflection, $annotation];
161
                }
162
            }
163
        }
164
165
        \gc_mem_caches();
166
167
        return $annotations;
168
    }
169
170
    /**
171
     *
172
     * @param Reflector $reflection
173
     *
174
     * @return iterable<object>
175
     */
176
    private function getAnnotations(Reflector $reflection, ListenerInterface $listener): iterable
177
    {
178
        $annotationClass = $listener->getAnnotation();
179
        $annotations     = [];
180
181
        switch (true) {
182
            case $reflection instanceof ReflectionClass:
183
                $annotations = $this->annotation->getClassMetadata($reflection);
184
185
                break;
186
187
            case $reflection instanceof ReflectionMethod:
188
                $annotations = $this->annotation->getFunctionMetadata($reflection);
189
190
                break;
191
192
            case $reflection instanceof ReflectionProperty:
193
                $annotations = $this->annotation->getPropertyMetadata($reflection);
194
195
                break;
196
197
            case $reflection instanceof ReflectionClassConstant:
198
                $annotations = $this->annotation->getConstantMetadata($reflection);
199
200
                break;
201
202
            case $reflection instanceof ReflectionParameter:
203
                $annotations = $this->annotation->getParameterMetadata($reflection);
204
        }
205
206
        foreach ($annotations as $annotation) {
207
            if ($annotation instanceof $annotationClass) {
208
                yield $annotation;
209
            }
210
        }
211
    }
212
213
    /**
214
     * @param ReflectionParameter[] $parameters
215
     * @param ListenerInterface     $listener
216
     *
217
     * @return iterable<int,object[]>
218
     */
219
    private function getMethodParameter(array $parameters, ListenerInterface $listener): iterable
220
    {
221
        foreach ($listener->getArguments() as $name) {
222
            foreach ($parameters as $parameter) {
223
                if ($parameter->getName() === $name) {
224
                    foreach ($this->getAnnotations($parameter, $listener) as $annotation) {
225
                        yield [$parameter, $annotation];
226
                    }
227
                }
228
            }
229
        }
230
    }
231
232
    /**
233
     * Finds classes in the given resource directory
234
     *
235
     * @param string $resource
236
     *
237
     * @return string[]
238
     */
239
    private function findClasses(string $resource): array
240
    {
241
        $files    = $this->findFiles($resource);
242
        $declared = \get_declared_classes();
243
244
        foreach ($files as $file) {
245
            require_once $file;
246
        }
247
248
        return \array_diff(\get_declared_classes(), $declared);
249
    }
250
251
    /**
252
     * Finds files in the given resource
253
     *
254
     * @param string $resource
255
     *
256
     * @return string[]
257
     */
258
    private function findFiles(string $resource): array
259
    {
260
        $flags = FilesystemIterator::CURRENT_AS_PATHNAME;
261
262
        $directory = new RecursiveDirectoryIterator($resource, $flags);
263
        $iterator  = new RecursiveIteratorIterator($directory);
264
        $files     = new RegexIterator($iterator, '/\.php$/');
265
266
        return \iterator_to_array($files);
267
    }
268
}
269