Passed
Pull Request — master (#32)
by Anatoly
02:45
created

AnnotationDirectoryLoader   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 195
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 19
eloc 54
c 0
b 0
f 0
dl 0
loc 195
ccs 63
cts 63
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setContainer() 0 3 1
A findFiles() 0 9 1
A load() 0 18 3
A findAnnotations() 0 24 3
A attach() 0 9 2
A getContainer() 0 3 1
A findClasses() 0 10 2
A initClass() 0 7 3
A initClasses() 0 7 2
A __construct() 0 6 1
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
namespace Sunrise\Http\Router\Loader;
13
14
/**
15
 * Import classes
16
 */
17
use Doctrine\Common\Annotations\SimpleAnnotationReader;
18
use Psr\Container\ContainerInterface;
19
use Sunrise\Http\Router\Annotation\Route as AnnotationRoute;
20
use Sunrise\Http\Router\Exception\InvalidLoadResourceException;
21
use Sunrise\Http\Router\RouteCollection;
22
use Sunrise\Http\Router\RouteCollectionInterface;
23
use Sunrise\Http\Router\RouteFactory;
24
use Sunrise\Http\Router\RouteFactoryInterface;
25
use FilesystemIterator;
26
use RecursiveDirectoryIterator;
27
use RecursiveIteratorIterator;
28
use ReflectionClass;
29
use RegexIterator;
30
31
/**
32
 * Import functions
33
 */
34
use function array_diff;
35
use function get_declared_classes;
36
use function is_dir;
37
use function iterator_to_array;
38
use function sprintf;
39
use function usort;
40
41
/**
42
 * AnnotationDirectoryLoader
43
 */
44
class AnnotationDirectoryLoader implements LoaderInterface
45
{
46
47
    /**
48
     * @var string[]
49
     */
50
    private $resources = [];
51
52
    /**
53
     * @var RouteFactoryInterface
54
     */
55
    private $routeFactory;
56
57
    /**
58
     * @var SimpleAnnotationReader
59
     */
60
    private $annotationReader;
61
62
    /**
63
     * @var null|ContainerInterface
64
     */
65
    private $container;
66
67
    /**
68
     * Constructor of the class
69
     *
70
     * @param null|RouteFactoryInterface $routeFactory
71
     */
72 21
    public function __construct(RouteFactoryInterface $routeFactory = null)
73
    {
74 21
        $this->routeFactory = $routeFactory ?? new RouteFactory();
75
76 21
        $this->annotationReader = new SimpleAnnotationReader();
77 21
        $this->annotationReader->addNamespace('Sunrise\Http\Router\Annotation');
78 21
    }
79
80
    /**
81
     * Gets the loader container
82
     *
83
     * @return null|ContainerInterface
84
     */
85 1
    public function getContainer() : ?ContainerInterface
86
    {
87 1
        return $this->container;
88
    }
89
90
    /**
91
     * Sets the given container to the loader
92
     *
93
     * @param ContainerInterface $container
94
     *
95
     * @return void
96
     */
97 2
    public function setContainer(ContainerInterface $container) : void
98
    {
99 2
        $this->container = $container;
100 2
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105 19
    public function attach($resource) : void
106
    {
107 19
        if (!is_dir($resource)) {
108 1
            throw new InvalidLoadResourceException(
109 1
                sprintf('The "%s" resource not found.', $resource)
110
            );
111
        }
112
113 18
        $this->resources[] = $resource;
114 18
    }
115
116
    /**
117
     * {@inheritDoc}
118
     */
119 18
    public function load() : RouteCollectionInterface
120
    {
121 18
        $routes = [];
122 18
        foreach ($this->resources as $resource) {
123 18
            $annotations = $this->findAnnotations($resource);
124 1
            foreach ($annotations as $annotation) {
125 1
                $routes[] = $this->routeFactory->createRoute(
126 1
                    $annotation->name,
127 1
                    $annotation->path,
128 1
                    $annotation->methods,
129 1
                    $this->initClass($annotation->source),
130 1
                    $this->initClasses(...$annotation->middlewares),
131 1
                    $annotation->attributes
132
                );
133
            }
134
        }
135
136 1
        return new RouteCollection(...$routes);
137
    }
138
139
    /**
140
     * Finds annotations in the given resource
141
     *
142
     * @param string $resource
143
     *
144
     * @return AnnotationRoute[]
145
     */
146 18
    private function findAnnotations(string $resource) : array
147
    {
148 18
        $classes = $this->findClasses($resource);
149
150 18
        $annotations = [];
151 18
        foreach ($classes as $class) {
152 18
            $annotation = $this->annotationReader->getClassAnnotation(
153 18
                new ReflectionClass($class),
154 18
                AnnotationRoute::class
155
            );
156
157 2
            if ($annotation) {
158 2
                AnnotationRoute::assertValidSource($class);
159
160 1
                $annotation->source = $class;
161 1
                $annotations[] = $annotation;
162
            }
163
        }
164
165
        usort($annotations, function ($a, $b) {
166 1
            return $b->priority <=> $a->priority;
167 1
        });
168
169 1
        return $annotations;
170
    }
171
172
    /**
173
     * Finds classes in the given resource
174
     *
175
     * @param string $resource
176
     *
177
     * @return string[]
178
     */
179 18
    private function findClasses(string $resource) : array
180
    {
181 18
        $files = $this->findFiles($resource);
182 18
        $declared = get_declared_classes();
183
184 18
        foreach ($files as $file) {
185 18
            require_once $file;
186
        }
187
188 18
        return array_diff(get_declared_classes(), $declared);
189
    }
190
191
    /**
192
     * Finds files in the given resource
193
     *
194
     * @param string $resource
195
     *
196
     * @return string[]
197
     */
198 18
    private function findFiles(string $resource) : array
199
    {
200 18
        $flags = FilesystemIterator::CURRENT_AS_PATHNAME;
201
202 18
        $directory = new RecursiveDirectoryIterator($resource, $flags);
203 18
        $iterator = new RecursiveIteratorIterator($directory);
204 18
        $files = new RegexIterator($iterator, '/\.php$/');
205
206 18
        return iterator_to_array($files);
207
    }
208
209
    /**
210
     * Initializes the given class
211
     *
212
     * @param string $class
213
     *
214
     * @return object
215
     */
216 1
    private function initClass(string $class)
217
    {
218 1
        if ($this->container && $this->container->has($class)) {
219 1
            return $this->container->get($class);
220
        }
221
222 1
        return new $class;
223
    }
224
225
    /**
226
     * Initializes the given classes
227
     *
228
     * @param string ...$classes
229
     *
230
     * @return object[]
231
     */
232 1
    private function initClasses(string ...$classes) : array
233
    {
234 1
        foreach ($classes as &$class) {
235 1
            $class = $this->initClass($class);
236
        }
237
238 1
        return $classes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $classes returns the type array<integer,string> which is incompatible with the documented return type array<mixed,object>.
Loading history...
239
    }
240
}
241