Passed
Pull Request — master (#34)
by Anatoly
02:04
created

AnnotationDirectoryLoader::setContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 Psr\SimpleCache\CacheInterface;
20
use Sunrise\Http\Router\Annotation\Route as AnnotationRoute;
21
use Sunrise\Http\Router\ExceptionFactory;
22
use Sunrise\Http\Router\RouteCollectionFactory;
23
use Sunrise\Http\Router\RouteCollectionFactoryInterface;
24
use Sunrise\Http\Router\RouteCollectionInterface;
25
use Sunrise\Http\Router\RouteFactory;
26
use Sunrise\Http\Router\RouteFactoryInterface;
27
use FilesystemIterator;
28
use RecursiveDirectoryIterator;
29
use RecursiveIteratorIterator;
30
use ReflectionClass;
31
use RegexIterator;
32
33
/**
34
 * Import functions
35
 */
36
use function array_diff;
37
use function get_declared_classes;
38
use function hash;
39
use function is_dir;
40
use function iterator_to_array;
41
use function uasort;
42
43
/**
44
 * AnnotationDirectoryLoader
45
 */
46
class AnnotationDirectoryLoader implements LoaderInterface
47
{
48
49
    /**
50
     * @var string[]
51
     */
52
    private $resources = [];
53
54
    /**
55
     * @var RouteCollectionFactoryInterface
56
     */
57
    private $collectionFactory;
58
59
    /**
60
     * @var RouteFactoryInterface
61
     */
62
    private $routeFactory;
63
64
    /**
65
     * @var SimpleAnnotationReader
66
     */
67
    private $annotationReader;
68
69
    /**
70
     * @var null|ContainerInterface
71
     */
72
    private $container;
73
74
    /**
75
     * @var null|CacheInterface
76
     */
77
    private $cache;
78
79
    /**
80
     * Constructor of the class
81
     *
82
     * @param null|RouteCollectionFactoryInterface $collectionFactory
83
     * @param null|RouteFactoryInterface $routeFactory
84
     */
85 23
    public function __construct(
86
        RouteCollectionFactoryInterface $collectionFactory = null,
87
        RouteFactoryInterface $routeFactory = null
88
    ) {
89 23
        $this->collectionFactory = $collectionFactory ?? new RouteCollectionFactory();
90 23
        $this->routeFactory = $routeFactory ?? new RouteFactory();
91
92 23
        $this->annotationReader = new SimpleAnnotationReader();
93 23
        $this->annotationReader->addNamespace('Sunrise\Http\Router\Annotation');
94 23
    }
95
96
    /**
97
     * Gets the loader container
98
     *
99
     * @return null|ContainerInterface
100
     */
101 1
    public function getContainer() : ?ContainerInterface
102
    {
103 1
        return $this->container;
104
    }
105
106
    /**
107
     * Gets the loader cache
108
     *
109
     * @return null|CacheInterface
110
     */
111 1
    public function getCache() : ?CacheInterface
112
    {
113 1
        return $this->cache;
114
    }
115
116
    /**
117
     * Sets the given container to the loader
118
     *
119
     * @param ContainerInterface $container
120
     *
121
     * @return void
122
     */
123 2
    public function setContainer(ContainerInterface $container) : void
124
    {
125 2
        $this->container = $container;
126 2
    }
127
128
    /**
129
     * Sets the given cache to the loader
130
     *
131
     * @param CacheInterface $cache
132
     *
133
     * @return void
134
     */
135 2
    public function setCache(CacheInterface $cache) : void
136
    {
137 2
        $this->cache = $cache;
138 2
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143 20
    public function attach($resource) : void
144
    {
145 20
        if (!is_dir($resource)) {
146 1
            throw (new ExceptionFactory)->invalidLoadResourceForFile($resource);
147
        }
148
149 19
        $this->resources[] = $resource;
150 19
    }
151
152
    /**
153
     * {@inheritDoc}
154
     */
155 19
    public function load() : RouteCollectionInterface
156
    {
157 19
        $annotations = [];
158 19
        foreach ($this->resources as $resource) {
159 19
            $annotations += $this->fetchAnnotations($resource);
160
        }
161
162 2
        $routes = [];
163 2
        foreach ($annotations as $class => $annotation) {
164 2
            $routes[] = $this->routeFactory->createRoute(
165 2
                $annotation->name,
166 2
                $annotation->path,
167 2
                $annotation->methods,
168 2
                $this->initClass($class),
169 2
                $this->initClasses(...$annotation->middlewares),
170 2
                $annotation->attributes
171
            );
172
        }
173
174 2
        return $this->collectionFactory->createCollection(...$routes);
175
    }
176
177
    /**
178
     * Fetches annotations for the given resource
179
     *
180
     * @param string $resource
181
     *
182
     * @return AnnotationRoute[]
183
     *
184
     * @throws \Psr\SimpleCache\CacheException
185
     */
186 19
    private function fetchAnnotations(string $resource) : array
187
    {
188 19
        if (!$this->cache) {
189 18
            return $this->findAnnotations($resource);
190
        }
191
192
        // some cache stores may have character restrictions for a key...
193 1
        $key = hash('md5', $resource);
194
195 1
        if (!$this->cache->has($key)) {
196 1
            $value = $this->findAnnotations($resource);
197 1
            $this->cache->set($key, $value);
198
        }
199
200 1
        return $this->cache->get($key);
201
    }
202
203
    /**
204
     * Finds annotations in the given resource
205
     *
206
     * @param string $resource
207
     *
208
     * @return AnnotationRoute[]
209
     */
210 19
    private function findAnnotations(string $resource) : array
211
    {
212 19
        $classes = $this->findClasses($resource);
213
214 19
        $annotations = [];
215 19
        foreach ($classes as $class) {
216 18
            $annotation = $this->annotationReader->getClassAnnotation(
217 18
                new ReflectionClass($class),
218 18
                AnnotationRoute::class
219
            );
220
221 2
            if ($annotation) {
222 2
                AnnotationRoute::assertValidSource($class);
223 1
                $annotations[$class] = $annotation;
224
            }
225
        }
226
227
        uasort($annotations, function ($a, $b) {
228 1
            return $b->priority <=> $a->priority;
229 2
        });
230
231 2
        return $annotations;
232
    }
233
234
    /**
235
     * Finds classes in the given resource
236
     *
237
     * @param string $resource
238
     *
239
     * @return string[]
240
     */
241 19
    private function findClasses(string $resource) : array
242
    {
243 19
        $files = $this->findFiles($resource);
244 19
        $declared = get_declared_classes();
245
246 19
        foreach ($files as $file) {
247 18
            require_once $file;
248
        }
249
250 19
        return array_diff(get_declared_classes(), $declared);
251
    }
252
253
    /**
254
     * Finds files in the given resource
255
     *
256
     * @param string $resource
257
     *
258
     * @return string[]
259
     */
260 19
    private function findFiles(string $resource) : array
261
    {
262 19
        $flags = FilesystemIterator::CURRENT_AS_PATHNAME;
263
264 19
        $directory = new RecursiveDirectoryIterator($resource, $flags);
265 19
        $iterator = new RecursiveIteratorIterator($directory);
266 19
        $files = new RegexIterator($iterator, '/\.php$/');
267
268 19
        return iterator_to_array($files);
269
    }
270
271
    /**
272
     * Initializes the given class
273
     *
274
     * @param string $class
275
     *
276
     * @return object
277
     */
278 2
    private function initClass(string $class)
279
    {
280 2
        if ($this->container && $this->container->has($class)) {
281 1
            return $this->container->get($class);
282
        }
283
284 2
        return new $class;
285
    }
286
287
    /**
288
     * Initializes the given classes
289
     *
290
     * @param string ...$classes
291
     *
292
     * @return object[]
293
     */
294 2
    private function initClasses(string ...$classes) : array
295
    {
296 2
        foreach ($classes as &$class) {
297 1
            $class = $this->initClass($class);
298
        }
299
300 2
        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...
301
    }
302
}
303