Passed
Push — 5.x ( 8bdbf0...b66c10 )
by Phil
01:48
created

Router::prepareRoutes()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 51
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 8.0291

Importance

Changes 0
Metric Value
eloc 25
c 0
b 0
f 0
dl 0
loc 51
ccs 24
cts 26
cp 0.9231
rs 8.4444
cc 8
nc 20
nop 1
crap 8.0291

How to fix   Long Method   

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
namespace League\Route;
6
7
use FastRoute\{DataGenerator, RouteCollector, RouteParser};
8
use InvalidArgumentException;
9
use League\Route\Middleware\{MiddlewareAwareInterface, MiddlewareAwareTrait};
10
use League\Route\Strategy\{ApplicationStrategy, OptionsHandlerInterface, StrategyAwareInterface, StrategyAwareTrait};
11
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
12
use Psr\Http\Server\RequestHandlerInterface;
13
use RuntimeException;
14
15
class Router implements
16
    MiddlewareAwareInterface,
17
    RouteCollectionInterface,
18
    StrategyAwareInterface,
19
    RequestHandlerInterface,
20
    RouteConditionHandlerInterface
21
{
22
    use MiddlewareAwareTrait;
23
    use RouteCollectionTrait;
24
    use RouteConditionHandlerTrait;
25
    use StrategyAwareTrait;
26
27
    protected const IDENTIFIER_SEPARATOR = "\t";
28
29
    /**
30
     * @var RouteGroup[]
31
     */
32
    protected $groups = [];
33
34
    /**
35
     * @var Route[]
36
     */
37
    protected $namedRoutes = [];
38
39
    /**
40
     * @var array
41
     */
42
    protected $patternMatchers = [
43
        '/{(.+?):number}/'        => '{$1:[0-9]+}',
44
        '/{(.+?):word}/'          => '{$1:[a-zA-Z]+}',
45
        '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}',
46
        '/{(.+?):slug}/'          => '{$1:[a-z0-9-]+}',
47
        '/{(.+?):uuid}/'          => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}'
48
    ];
49
50
    /**
51
     * @var RouteCollector
52
     */
53
    protected $routeCollector;
54
55
    /**
56
     * @var Route[]
57
     */
58
    protected $routes = [];
59
60
    /**
61
     * @var bool
62
     */
63
    protected $routesPrepared = false;
64
65
    /**
66
     * @var array
67
     */
68
    protected $routesData = [];
69
70 72
    public function __construct(?RouteCollector $routeCollector = null)
71
    {
72 72
        $this->routeCollector = $routeCollector ?? new RouteCollector(
73 72
            new RouteParser\Std(),
74 72
            new DataGenerator\GroupCountBased()
75
        );
76 72
    }
77
78 3
    public function addPatternMatcher(string $alias, string $regex): self
79
    {
80 3
        $pattern = '/{(.+?):' . $alias . '}/';
81 3
        $regex = '{$1:' . $regex . '}';
82 3
        $this->patternMatchers[$pattern] = $regex;
83 3
        return $this;
84
    }
85
86 15
    public function group(string $prefix, callable $group): RouteGroup
87
    {
88 15
        $group = new RouteGroup($prefix, $group, $this);
89 15
        $this->groups[] = $group;
90 15
        return $group;
91
    }
92
93 54
    public function dispatch(ServerRequestInterface $request): ResponseInterface
94
    {
95 54
        if (false === $this->routesPrepared) {
96 51
            $this->prepareRoutes($request);
97
        }
98
99
        /** @var Dispatcher $dispatcher */
100 54
        $dispatcher = (new Dispatcher($this->routesData))->setStrategy($this->getStrategy());
0 ignored issues
show
Bug introduced by
It seems like $this->getStrategy() can also be of type null; however, parameter $strategy of League\Route\Dispatcher::setStrategy() does only seem to accept League\Route\Strategy\StrategyInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

100
        $dispatcher = (new Dispatcher($this->routesData))->setStrategy(/** @scrutinizer ignore-type */ $this->getStrategy());
Loading history...
101
102 54
        foreach ($this->getMiddlewareStack() as $middleware) {
103 3
            if (is_string($middleware)) {
104 3
                $dispatcher->lazyMiddleware($middleware);
105 3
                continue;
106
            }
107
108 3
            $dispatcher->middleware($middleware);
109
        }
110
111 54
        return $dispatcher->dispatchRequest($request);
112
    }
113
114 9
    public function getNamedRoute(string $name): Route
115
    {
116 9
        if (!$this->routesPrepared) {
117 9
            $this->collectGroupRoutes();
118
        }
119
120 9
        $this->buildNameIndex();
121
122 9
        if (isset($this->namedRoutes[$name])) {
123 6
            return $this->namedRoutes[$name];
124
        }
125
126 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
127
    }
128
129 3
    public function handle(ServerRequestInterface $request): ResponseInterface
130
    {
131 3
        return $this->dispatch($request);
132
    }
133
134 57
    public function map(string $method, string $path, $handler): Route
135
    {
136 57
        $path  = sprintf('/%s', ltrim($path, '/'));
137 57
        $route = new Route($method, $path, $handler);
138
139 57
        $this->routes[] = $route;
140
141 57
        return $route;
142
    }
143
144 54
    public function prepareRoutes(ServerRequestInterface $request): void
145
    {
146 54
        if ($this->getStrategy() === null) {
147 36
            $this->setStrategy(new ApplicationStrategy());
148
        }
149
150 54
        $this->processGroups($request);
151 54
        $this->buildNameIndex();
152
153 54
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
154 54
        $options = [];
155
156
        /** @var Route $route */
157 54
        foreach ($routes as $route) {
158
            // this allows for the same route to be mapped across different routes/hosts etc
159 48
            if (false === $this->isExtraConditionMatch($route, $request)) {
160 12
                continue;
161
            }
162
163 39
            if ($route->getStrategy() === null) {
164 30
                $route->setStrategy($this->getStrategy());
165
            }
166
167 39
            $this->routeCollector->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
168
169
            // global strategy must be an OPTIONS handler to automatically generate OPTIONS route
170 39
            if (!($this->getStrategy() instanceof OptionsHandlerInterface)) {
171 30
                continue;
172
            }
173
174
            // need a messy but useful identifier to determine what methods to respond with on OPTIONS
175 9
            $identifier = $route->getScheme() . static::IDENTIFIER_SEPARATOR . $route->getHost()
176 9
                . static::IDENTIFIER_SEPARATOR . $route->getPort() . static::IDENTIFIER_SEPARATOR . $route->getPath();
177
178
            // if there is a defined OPTIONS route, do not generate one
179 9
            if ('OPTIONS' === $route->getMethod()) {
180
                unset($options[$identifier]);
181
                continue;
182
            }
183
184 9
            if (!isset($options[$identifier])) {
185 9
                $options[$identifier] = [];
186
            }
187
188 9
            $options[$identifier][] = $route->getMethod();
189
        }
190
191 54
        $this->buildOptionsRoutes($options);
192
193 54
        $this->routesPrepared = true;
194 54
        $this->routesData = $this->routeCollector->getData();
195 54
    }
196
197 63
    protected function buildNameIndex(): void
198
    {
199 63
        foreach ($this->routes as $key => $route) {
200 54
            if ($route->getName() !== null) {
201 6
                unset($this->routes[$key]);
202 6
                $this->namedRoutes[$route->getName()] = $route;
203
            }
204
        }
205 63
    }
206
207 54
    protected function buildOptionsRoutes(array $options): void
208
    {
209 54
        if (!($this->getStrategy() instanceof OptionsHandlerInterface)) {
210 42
            return;
211
        }
212
213
        /** @var OptionsHandlerInterface $strategy */
214 12
        $strategy = $this->getStrategy();
215
216 12
        foreach ($options as $identifier => $methods) {
217 9
            [$scheme, $host, $port, $path] = explode(static::IDENTIFIER_SEPARATOR, $identifier);
218 9
            $route = new Route('OPTIONS', $path, $strategy->getOptionsCallable($methods));
219
220 9
            if (!empty($scheme)) {
221
                $route->setScheme($scheme);
222
            }
223
224 9
            if (!empty($host)) {
225
                $route->setHost($host);
226
            }
227
228 9
            if (!empty($port)) {
229
                $route->setPort($port);
0 ignored issues
show
Bug introduced by
$port of type string is incompatible with the type integer expected by parameter $port of League\Route\Route::setPort(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

229
                $route->setPort(/** @scrutinizer ignore-type */ $port);
Loading history...
230
            }
231
232 9
            $this->routeCollector->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
233
        }
234 12
    }
235
236 9
    protected function collectGroupRoutes(): void
237
    {
238 9
        foreach ($this->groups as $group) {
239 3
            $group();
240
        }
241 9
    }
242
243 54
    protected function processGroups(ServerRequestInterface $request): void
244
    {
245 54
        $activePath = $request->getUri()->getPath();
246
247 54
        foreach ($this->groups as $key => $group) {
248
            // we want to determine if we are technically in a group even if the
249
            // route is not matched so exceptions are handled correctly
250
            if (
251 9
                $group->getStrategy() !== null
252 9
                && strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0
253
            ) {
254 6
                $this->setStrategy($group->getStrategy());
255
            }
256
257 9
            unset($this->groups[$key]);
258 9
            $group();
259
        }
260 54
    }
261
262 39
    protected function parseRoutePath(string $path): string
263
    {
264 39
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
265
    }
266
}
267