Completed
Pull Request — master (#178)
by Phil
01:48
created

Router::prepRoutes()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 9.648

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 12
cts 15
cp 0.8
rs 8.0555
c 0
b 0
f 0
cc 9
nc 6
nop 1
crap 9.648
1
<?php declare(strict_types=1);
2
3
namespace League\Route;
4
5
use InvalidArgumentException;
6
use FastRoute\{DataGenerator, RouteCollector, RouteParser};
7
use League\Route\Strategy\{ApplicationStrategy, StrategyAwareInterface, StrategyAwareTrait};
8
use League\Route\Middleware\{MiddlewareAwareInterface, MiddlewareAwareTrait};
9
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
10
11
class Router extends RouteCollector implements
12
    MiddlewareAwareInterface,
13
    RouteCollectionInterface,
14
    StrategyAwareInterface
15
{
16
    use MiddlewareAwareTrait;
17
    use RouteCollectionTrait;
18
    use StrategyAwareTrait;
19
20
    /**
21
     * @var \League\Route\Route[]
22
     */
23
    protected $routes = [];
24
25
    /**
26
     * @var \League\Route\Route[]
27
     */
28
    protected $namedRoutes = [];
29
30
    /**
31
     * @var \League\Route\RouteGroup[]
32
     */
33
    protected $groups = [];
34
35
    /**
36
     * @var array
37
     */
38
    protected $patternMatchers = [
39
        '/{(.+?):number}/'        => '{$1:[0-9]+}',
40
        '/{(.+?):word}/'          => '{$1:[a-zA-Z]+}',
41
        '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}',
42
        '/{(.+?):slug}/'          => '{$1:[a-z0-9-]+}',
43
        '/{(.+?):uuid}/'          => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}'
44
    ];
45
46
    /**
47
     * Constructor.
48
     *
49
     * @param ?\FastRoute\RouteParser   $parser
0 ignored issues
show
Documentation introduced by
The doc-type ?\FastRoute\RouteParser could not be parsed: Unknown type name "?\FastRoute\RouteParser" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
50
     * @param ?\FastRoute\DataGenerator $generator
0 ignored issues
show
Documentation introduced by
The doc-type ?\FastRoute\DataGenerator could not be parsed: Unknown type name "?\FastRoute\DataGenerator" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
51
     */
52 39
    public function __construct(?RouteParser $parser = null, ?DataGenerator $generator = null)
53
    {
54
        // build parent route collector
55 39
        $parser    = ($parser) ?? new RouteParser\Std;
56 39
        $generator = ($generator) ?? new DataGenerator\GroupCountBased;
57 39
        parent::__construct($parser, $generator);
58 39
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 24
    public function map(string $method, string $path, $handler) : Route
64
    {
65 24
        $path  = sprintf('/%s', ltrim($path, '/'));
66 24
        $route = new Route($method, $path, $handler);
67
68 24
        $this->routes[] = $route;
69
70 24
        return $route;
71
    }
72
73
    /**
74
     * Add a group of routes to the collection.
75
     *
76
     * @param string   $prefix
77
     * @param callable $group
78
     *
79
     * @return \League\Route\RouteGroup
80
     */
81 3
    public function group(string $prefix, callable $group) : RouteGroup
82
    {
83 3
        $group          = new RouteGroup($prefix, $group, $this);
84 3
        $this->groups[] = $group;
85
86 3
        return $group;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 24
    public function dispatch(ServerRequestInterface $request) : ResponseInterface
93
    {
94 24
        if (is_null($this->getStrategy())) {
95 12
            $this->setStrategy(new ApplicationStrategy);
96
        }
97
98 24
        $this->prepRoutes($request);
99
100 24
        return (new Dispatcher($this->getData()))
101 24
            ->middlewares($this->getMiddlewareStack())
102 24
            ->setStrategy($this->getStrategy())
103 24
            ->dispatchRequest($request)
104
        ;
105
    }
106
107
    /**
108
     * Prepare all routes, build name index and filter out none matching
109
     * routes before being passed off to the parser.
110
     *
111
     * @param \Psr\Http\Message\ServerRequestInterface $request
112
     *
113
     * @return void
114
     */
115 24
    protected function prepRoutes(ServerRequestInterface $request) : void
116
    {
117 24
        $this->processGroups($request);
118 24
        $this->buildNameIndex();
119
120 24
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
121
122 24
        foreach ($routes as $key => $route) {
123
            // check for scheme condition
124 18
            if (! is_null($route->getScheme()) && $route->getScheme() !== $request->getUri()->getScheme()) {
125
                continue;
126
            }
127
128
            // check for domain condition
129 18
            if (! is_null($route->getHost()) && $route->getHost() !== $request->getUri()->getHost()) {
130
                continue;
131
            }
132
133
            // check for port condition
134 18
            if (! is_null($route->getPort()) && $route->getPort() !== $request->getUri()->getPort()) {
135
                continue;
136
            }
137
138 18
            if (is_null($route->getStrategy())) {
139 18
                $route->setStrategy($this->getStrategy());
140
            }
141
142 18
            $this->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
143
        }
144 24
    }
145
146
    /**
147
     * Build an index of named routes.
148
     *
149
     * @return void
150
     */
151 30
    protected function buildNameIndex() : void
152
    {
153 30
        foreach ($this->routes as $key => $route) {
154 21
            if (! is_null($route->getName())) {
155 3
                unset($this->routes[$key]);
156 21
                $this->namedRoutes[$route->getName()] = $route;
157
            }
158
        }
159 30
    }
160
161
    /**
162
     * Process all groups, and determine if we are using a group's strategy.
163
     *
164
     * @param ?\Psr\Http\Message\ServerRequestInterface $request
0 ignored issues
show
Documentation introduced by
The doc-type ?\Psr\Http\Message\ServerRequestInterface could not be parsed: Unknown type name "?\Psr\Http\Message\ServerRequestInterface" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
165
     *
166
     * @return void
167
     */
168 24
    protected function processGroups(?ServerRequestInterface $request = null) : void
169
    {
170 24
        $activePath = $request->getUri()->getPath();
171
172 24
        foreach ($this->groups as $key => $group) {
173
            // we want to determine if we are technically in a group even if the
174
            // route is not matched so exceptions are handled correctly
175
            if (strncmp($activePath, $group->getPrefix, strlen($group->getPrefix)) === 0
0 ignored issues
show
Bug introduced by
The property getPrefix does not seem to exist. Did you mean prefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
176
                && ! is_null($group->getStrategy())
177
            ) {
178
                $this->setStrategy($group->getStrategy());
179
            }
180
181
            unset($this->groups[$key]);
182
            $group();
183
        }
184 24
    }
185
186
    /**
187
     * Get named route.
188
     *
189
     * @param string $name
190
     *
191
     * @throws \InvalidArgumentException when no route of the provided name exists.
192
     *
193
     * @return \League\Route\Route
194
     */
195 6
    public function getNamedRoute(string $name) : Route
196
    {
197 6
        $this->buildNameIndex();
198
199 6
        if (array_key_exists($name, $this->namedRoutes)) {
200 3
            return $this->namedRoutes[$name];
201
        }
202
203 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
204
    }
205
206
    /**
207
     * Add a convenient pattern matcher to the internal array for use with all routes.
208
     *
209
     * @param string $alias
210
     * @param string $regex
211
     *
212
     * @return self
213
     */
214 3
    public function addPatternMatcher(string $alias, string $regex) : self
215
    {
216 3
        $pattern = '/{(.+?):' . $alias . '}/';
217 3
        $regex   = '{$1:' . $regex . '}';
218
219 3
        $this->patternMatchers[$pattern] = $regex;
220
221 3
        return $this;
222
    }
223
224
    /**
225
     * Convenience method to convert pre-defined key words in to regex strings.
226
     *
227
     * @param string $path
228
     *
229
     * @return string
230
     */
231 18
    protected function parseRoutePath(string $path) : string
232
    {
233 18
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
234
    }
235
}
236