Completed
Push — master ( b3457c...c694d9 )
by Phil
02:30
created

Router   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 7
dl 0
loc 228
ccs 64
cts 64
cp 1
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A map() 0 9 1
A group() 0 7 1
A dispatch() 0 14 2
A buildNameIndex() 0 9 3
B prepRoutes() 0 30 9
A processGroups() 0 17 4
A getNamedRoute() 0 10 2
A addPatternMatcher() 0 9 1
A parseRoutePath() 0 4 1
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
50
     * @param \FastRoute\DataGenerator $generator
51
     */
52 40
    public function __construct(?RouteParser $parser = null, ?DataGenerator $generator = null)
53
    {
54
        // build parent route collector
55 40
        $parser    = ($parser) ?? new RouteParser\Std;
56 40
        $generator = ($generator) ?? new DataGenerator\GroupCountBased;
57 40
        parent::__construct($parser, $generator);
58 40
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 30
    public function map(string $method, string $path, $handler) : Route
64
    {
65 30
        $path  = sprintf('/%s', ltrim($path, '/'));
66 30
        $route = new Route($method, $path, $handler);
67
68 30
        $this->routes[] = $route;
69
70 30
        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 8
    public function group(string $prefix, callable $group) : RouteGroup
82
    {
83 8
        $group          = new RouteGroup($prefix, $group, $this);
84 8
        $this->groups[] = $group;
85
86 8
        return $group;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 30
    public function dispatch(ServerRequestInterface $request) : ResponseInterface
93
    {
94 30
        if (is_null($this->getStrategy())) {
95 18
            $this->setStrategy(new ApplicationStrategy);
96
        }
97
98 30
        $this->prepRoutes($request);
99
100 30
        return (new Dispatcher($this->getData()))
101 30
            ->middlewares($this->getMiddlewareStack())
102 30
            ->setStrategy($this->getStrategy())
103 30
            ->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 30
    protected function prepRoutes(ServerRequestInterface $request) : void
116
    {
117 30
        $this->processGroups($request);
118 30
        $this->buildNameIndex();
119
120 30
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
121
122 30
        foreach ($routes as $key => $route) {
123
            // check for scheme condition
124 26
            if (! is_null($route->getScheme()) && $route->getScheme() !== $request->getUri()->getScheme()) {
125 2
                continue;
126
            }
127
128
            // check for domain condition
129 24
            if (! is_null($route->getHost()) && $route->getHost() !== $request->getUri()->getHost()) {
130 2
                continue;
131
            }
132
133
            // check for port condition
134 22
            if (! is_null($route->getPort()) && $route->getPort() !== $request->getUri()->getPort()) {
135 2
                continue;
136
            }
137
138 20
            if (is_null($route->getStrategy())) {
139 14
                $route->setStrategy($this->getStrategy());
140
            }
141
142 20
            $this->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
143
        }
144 30
    }
145
146
    /**
147
     * Build an index of named routes.
148
     *
149
     * @return void
150
     */
151 34
    protected function buildNameIndex() : void
152
    {
153 34
        foreach ($this->routes as $key => $route) {
154 28
            if (! is_null($route->getName())) {
1 ignored issue
show
Bug introduced by
Consider using $route->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
155 2
                unset($this->routes[$key]);
156 28
                $this->namedRoutes[$route->getName()] = $route;
1 ignored issue
show
Bug introduced by
Consider using $route->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
157
            }
158
        }
159 34
    }
160
161
    /**
162
     * Process all groups
163
     *
164
     * Adds all of the group routes to the collection and determines if the group
165
     * strategy should be be used.
166
     *
167
     * @param \Psr\Http\Message\ServerRequestInterface $request
168
     *
169
     * @return void
170
     */
171 30
    protected function processGroups(ServerRequestInterface $request) : void
172
    {
173 30
        $activePath = $request->getUri()->getPath();
174
175 30
        foreach ($this->groups as $key => $group) {
176
            // we want to determine if we are technically in a group even if the
177
            // route is not matched so exceptions are handled correctly
178 6
            if (strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0
179 6
                && ! is_null($group->getStrategy())
180
            ) {
181 4
                $this->setStrategy($group->getStrategy());
182
            }
183
184 6
            unset($this->groups[$key]);
185 6
            $group();
186
        }
187 30
    }
188
189
    /**
190
     * Get a named route
191
     *
192
     * @param string $name
193
     *
194
     * @throws \InvalidArgumentException when no route of the provided name exists.
195
     *
196
     * @return \League\Route\Route
197
     */
198 4
    public function getNamedRoute(string $name) : Route
199
    {
200 4
        $this->buildNameIndex();
201
202 4
        if (isset($this->namedRoutes[$name])) {
203 2
            return $this->namedRoutes[$name];
204
        }
205
206 2
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
207
    }
208
209
    /**
210
     * Add a convenient pattern matcher to the internal array for use with all routes
211
     *
212
     * @param string $alias
213
     * @param string $regex
214
     *
215
     * @return self
216
     */
217 2
    public function addPatternMatcher(string $alias, string $regex) : self
218
    {
219 2
        $pattern = '/{(.+?):' . $alias . '}/';
220 2
        $regex   = '{$1:' . $regex . '}';
221
222 2
        $this->patternMatchers[$pattern] = $regex;
223
224 2
        return $this;
225
    }
226
227
    /**
228
     * Replace word patterns with regex in route path
229
     *
230
     * @param string $path
231
     *
232
     * @return string
233
     */
234 20
    protected function parseRoutePath(string $path) : string
235
    {
236 20
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
237
    }
238
}
239