Passed
Push — master ( 81799a...502582 )
by Gennady
01:52
created

App::addRoutes()   B

Complexity

Conditions 11
Paths 24

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 28
cts 28
cp 1
rs 7.3166
c 0
b 0
f 0
cc 11
nc 24
nop 1
crap 11

How to fix   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 the slim-annotation-based package.
7
 *
8
 * (c) Gennady Knyazkin <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Redreams\Slim;
15
16
use Doctrine\Common\Annotations\AnnotationReader;
17
use Doctrine\Common\Annotations\AnnotationRegistry;
18
use Doctrine\Common\Annotations\CachedReader;
19
use Doctrine\Common\Annotations\Reader;
20
use Doctrine\Common\Cache\FilesystemCache;
21
use FilesystemIterator;
22
use Generator;
23
use Psr\Container\ContainerInterface;
24
use RecursiveCallbackFilterIterator;
25
use RecursiveDirectoryIterator;
26
use RecursiveIteratorIterator;
27
use Redreams\ClassFinder\ClassFinder;
28
use Redreams\Slim\Annotation\Route;
29
use Redreams\Slim\Exception\InvalidArgumentException;
30
use ReflectionClass;
31
use ReflectionMethod;
32
use ReflectionParameter;
33
use Slim\App as SlimApp;
34
use Slim\Router;
35
use SplFileInfo;
36
use function file_get_contents;
37
use function is_dir;
38
use function rtrim;
39
use function sprintf;
40
use function trim;
41
42
/**
43
 * Class App
44
 *
45
 * @author Gennady Knyazkin <[email protected]>
46
 */
47
class App extends SlimApp
48
{
49
    /**
50
     * @var Reader
51
     */
52
    protected $reader;
53
54
    /**
55
     * App constructor.
56
     *
57
     * @param string                   $controllersDir
58
     * @param array|ContainerInterface $container
59
     *
60
     * @throws InvalidArgumentException
61
     * @throws \InvalidArgumentException
62
     * @throws \Doctrine\Common\Annotations\AnnotationException
63
     * @throws \ReflectionException
64
     */
65 6
    public function __construct(string $controllersDir, $container = [])
66
    {
67 6
        parent::__construct($container);
68 6
        if (!is_dir($controllersDir)) {
69 1
            throw new InvalidArgumentException(
70 1
                sprintf('Controllers directory "%s" does not exists', $controllersDir)
71
            );
72
        }
73 5
        AnnotationRegistry::registerLoader('class_exists');
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\Common\Annotati...istry::registerLoader() has been deprecated with message: this method is deprecated and will be removed in doctrine/annotations 2.0 autoloading should be deferred to the globally registered autoloader by then. For now, use @example AnnotationRegistry::registerLoader('class_exists')

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
74 5
        $this->reader = $this->createAnnotationReader();
75 5
        $this->loadControllers($controllersDir);
76 5
    }
77
78
79
    /**
80
     * @param string $controllerDir
81
     *
82
     * @return void
83
     * @throws \InvalidArgumentException
84
     * @throws \ReflectionException
85
     */
86 5
    protected function loadControllers(string $controllerDir): void
87
    {
88
        /** @var SplFileInfo $file */
89 5
        foreach ($this->getControllerFiles($controllerDir) as $file) {
90 5
            if (($class = ClassFinder::find(file_get_contents($file->getRealPath()))) !== null) {
91 5
                $this->addRoutes($class);
92
            }
93 5
            gc_mem_caches();
94
        }
95 5
    }
96
97
    /**
98
     * @param string $class
99
     *
100
     * @return void
101
     * @throws \ReflectionException
102
     * @throws \InvalidArgumentException
103
     */
104 5
    protected function addRoutes(string $class): void
105
    {
106 5
        static $instance;
107
        /** @var Router $router */
108 5
        $router = $this->getContainer()->get('router');
109 5
        $settings = $this->getContainer()->get('settings');
110 5
        $relectionClass = new ReflectionClass($class);
111
        /** @var Route $classRoute */
112 5
        $classRoute = $this->reader->getClassAnnotation($relectionClass, Route::class);
113 5
        $pattern = '/';
114 5
        if ($classRoute !== null && !empty(trim($classRoute->getPattern(), '/'))) {
115 5
            $pattern = sprintf('/%s/', trim($classRoute->getPattern(), '/'));
116
        }
117 5
        foreach ($relectionClass->getMethods() as $reflectionMethod) {
118 5
            $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
119
            /** @var Route $methodRoute */
120 5
            if ($reflectionMethod->isStatic()
121 5
                || !$reflectionMethod->isPublic()
122 5
                || ($methodRoute = $this->getAnnotation($methodAnnotations, Route::class)) === null
123
            ) {
124 5
                continue;
125
            }
126 5
            if ($instance === null) {
127 5
                $instance = $this->createControllerInstance($relectionClass);
128
            }
129 5
            $methods = $methodRoute->getMethods() ?? ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
130 5
            $route = $router->map(
131 5
                $methods,
132 5
                rtrim($pattern.ltrim($methodRoute->getPattern(), '/'), '/') ?: '/',
133 5
                $this->createRouteCallback($instance, $reflectionMethod, $methodAnnotations)
134
            );
135 5
            $route->setOutputBuffering($settings['outputBuffering']);
136 5
            if ($methodRoute->getName() !== null) {
137 5
                $route->setName($methodRoute->getName());
138
            }
139
140
        }
141 5
        if ($instance !== null) {
142 5
            $instance = null;
143
        }
144 5
    }
145
146
    /**
147
     * @param                  $controllerInstance
148
     * @param ReflectionMethod $method
149
     * @param array            $annotations
150
     *
151
     * @return callable
152
     */
153 5
    protected function createRouteCallback(
154
        $controllerInstance,
155
        ReflectionMethod $method,
156
        array $annotations = []
0 ignored issues
show
Unused Code introduced by
The parameter $annotations is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
157
    ): callable {
158 5
        return [$controllerInstance, $method->getName()];
159
    }
160
161
    /**
162
     * @param array  $annotations
163
     * @param string $name
164
     *
165
     * @return object|null
166
     */
167 5
    protected function getAnnotation(array $annotations, string $name)
168
    {
169 5
        foreach ($annotations as $annotation) {
170 5
            if ($annotation instanceof $name) {
171 5
                return $annotation;
172
            }
173
        }
174
175 5
        return null;
176
    }
177
178
    /**
179
     * @param string $controllerDir
180
     *
181
     * @return Generator|SplFileInfo
182
     */
183 5
    protected function getControllerFiles(string $controllerDir)
184
    {
185 5
        $directoryIterator = new RecursiveDirectoryIterator(
186 5
            $controllerDir,
187 5
            FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS
188
        );
189
        $iterator = new RecursiveCallbackFilterIterator($directoryIterator, function (SplFileInfo $file) {
190 5
            return $file->isFile() && $file->getExtension() === 'php';
191 5
        });
192
        /** @var SplFileInfo $file */
193 5
        foreach (new RecursiveIteratorIterator($iterator) as $file) {
194 5
            yield $file;
195
        }
196 5
    }
197
198
    /**
199
     * @param ReflectionClass $relectionClass
200
     *
201
     * @return object
202
     */
203 5
    protected function createControllerInstance(ReflectionClass $relectionClass)
204
    {
205 5
        $args = [];
206 5
        $constructor = $relectionClass->getConstructor();
207 5
        if ($constructor !== null && $constructor->getNumberOfParameters() > 0) {
208
            /** @var ReflectionParameter $firstParameter */
209 5
            $firstParameter = $constructor->getParameters()[0];
210 5
            $parameterClass = $firstParameter->getClass();
211 5
            if ($parameterClass !== null
212 5
                && (($parameterClass->isInterface() && $parameterClass->getName() === ContainerInterface::class)
213 5
                    || $parameterClass->implementsInterface(ContainerInterface::class))
214
            ) {
215 5
                $args[] = $this->getContainer();
216
            }
217
        }
218
219 5
        return $relectionClass->newInstanceArgs($args);
220
    }
221
222
    /**
223
     * @return Reader
224
     * @throws \Doctrine\Common\Annotations\AnnotationException
225
     * @throws \InvalidArgumentException
226
     */
227 5
    protected function createAnnotationReader(): Reader
228
    {
229 5
        $settings = $this->getContainer()->get('settings');
230 5
        if (isset($settings['routerCacheDir'])) {
231 1
            return new CachedReader(
232 1
                new AnnotationReader(),
233 1
                new FilesystemCache($settings['routerCacheDir']),
234 1
                false
235
            );
236
        }
237
238 5
        return new AnnotationReader();
239
    }
240
}
241