Router::addPatternMatcher()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php declare(strict_types=1);
2
3
namespace League\Route;
4
5
use FastRoute\{DataGenerator, RouteCollector, RouteParser};
6
use InvalidArgumentException;
7
use League\Route\Middleware\{MiddlewareAwareInterface, MiddlewareAwareTrait};
8
use League\Route\Strategy\{ApplicationStrategy, StrategyAwareInterface, StrategyAwareTrait};
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 Route[]
22
     */
23
    protected $routes = [];
24
25
    /**
26
     * @var Route[]
27
     */
28
    protected $namedRoutes = [];
29
30
    /**
31
     * @var 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 RouteParser $parser
50
     * @param DataGenerator $generator
51
     */
52 63
    public function __construct(?RouteParser $parser = null, ?DataGenerator $generator = null)
53
    {
54
        // build parent route collector
55 63
        $parser    = $parser ?? new RouteParser\Std;
56 63
        $generator = $generator ?? new DataGenerator\GroupCountBased;
57 63
        parent::__construct($parser, $generator);
58 63
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 45
    public function map(string $method, string $path, $handler): Route
64
    {
65 45
        $path  = sprintf('/%s', ltrim($path, '/'));
66 45
        $route = new Route($method, $path, $handler);
67
68 45
        $this->routes[] = $route;
69
70 45
        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 RouteGroup
80
     */
81 12
    public function group(string $prefix, callable $group): RouteGroup
82
    {
83 12
        $group          = new RouteGroup($prefix, $group, $this);
84 12
        $this->groups[] = $group;
85
86 12
        return $group;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 48
    public function dispatch(ServerRequestInterface $request): ResponseInterface
93
    {
94 48
        if ($this->getStrategy() === null) {
95 30
            $this->setStrategy(new ApplicationStrategy);
96
        }
97
98 48
        $this->prepRoutes($request);
99
100
        /** @var Dispatcher $dispatcher */
101 48
        $dispatcher = (new Dispatcher($this->getData()))->setStrategy($this->getStrategy());
102
103 48
        foreach ($this->getMiddlewareStack() as $middleware) {
104 3
            if (is_string($middleware)) {
105 3
                $dispatcher->lazyMiddleware($middleware);
106 3
                continue;
107
            }
108
109 3
            $dispatcher->middleware($middleware);
110
        }
111
112 48
        return $dispatcher->dispatchRequest($request);
113
    }
114
115
    /**
116
     * Prepare all routes, build name index and filter out none matching
117
     * routes before being passed off to the parser.
118
     *
119
     * @param ServerRequestInterface $request
120
     *
121
     * @return void
122
     */
123 48
    protected function prepRoutes(ServerRequestInterface $request): void
124
    {
125 48
        $this->processGroups($request);
126 48
        $this->buildNameIndex();
127
128 48
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
129
130
        /** @var Route $route */
131 48
        foreach ($routes as $key => $route) {
132
            // check for scheme condition
133 39
            $scheme = $route->getScheme();
134 39
            if ($scheme !== null && $scheme !== $request->getUri()->getScheme()) {
135 3
                continue;
136
            }
137
138
            // check for domain condition
139 36
            $host = $route->getHost();
140 36
            if ($host !== null && $host !== $request->getUri()->getHost()) {
141 3
                continue;
142
            }
143
144
            // check for port condition
145 33
            $port = $route->getPort();
146 33
            if ($port !== null && $port !== $request->getUri()->getPort()) {
147 3
                continue;
148
            }
149
150 30
            if ($route->getStrategy() === null) {
151 21
                $route->setStrategy($this->getStrategy());
152
            }
153
154 30
            $this->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
155
        }
156 48
    }
157
158
    /**
159
     * Build an index of named routes.
160
     *
161
     * @return void
162
     */
163 54
    protected function buildNameIndex(): void
164
    {
165 54
        foreach ($this->routes as $key => $route) {
166 42
            if ($route->getName() !== null) {
167 3
                unset($this->routes[$key]);
168 3
                $this->namedRoutes[$route->getName()] = $route;
169
            }
170
        }
171 54
    }
172
173
    /**
174
     * Process all groups
175
     *
176
     * Adds all of the group routes to the collection and determines if the group
177
     * strategy should be be used.
178
     *
179
     * @param ServerRequestInterface $request
180
     *
181
     * @return void
182
     */
183 48
    protected function processGroups(ServerRequestInterface $request): void
184
    {
185 48
        $activePath = $request->getUri()->getPath();
186
187 48
        foreach ($this->groups as $key => $group) {
188
            // we want to determine if we are technically in a group even if the
189
            // route is not matched so exceptions are handled correctly
190 9
            if ($group->getStrategy() !== null
191 9
                && strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0
192
            ) {
193 6
                $this->setStrategy($group->getStrategy());
194
            }
195
196 9
            unset($this->groups[$key]);
197 9
            $group();
198
        }
199 48
    }
200
201
    /**
202
     * Get a named route
203
     *
204
     * @param string $name
205
     *
206
     * @return Route
207
     *
208
     * @throws InvalidArgumentException when no route of the provided name exists
209
     */
210 6
    public function getNamedRoute(string $name): Route
211
    {
212 6
        $this->buildNameIndex();
213
214 6
        if (isset($this->namedRoutes[$name])) {
215 3
            return $this->namedRoutes[$name];
216
        }
217
218 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
219
    }
220
221
    /**
222
     * Add a convenient pattern matcher to the internal array for use with all routes
223
     *
224
     * @param string $alias
225
     * @param string $regex
226
     *
227
     * @return self
228
     */
229 3
    public function addPatternMatcher(string $alias, string $regex): self
230
    {
231 3
        $pattern = '/{(.+?):' . $alias . '}/';
232 3
        $regex   = '{$1:' . $regex . '}';
233
234 3
        $this->patternMatchers[$pattern] = $regex;
235
236 3
        return $this;
237
    }
238
239
    /**
240
     * Replace word patterns with regex in route path
241
     *
242
     * @param string $path
243
     *
244
     * @return string
245
     */
246 30
    protected function parseRoutePath(string $path): string
247
    {
248 30
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
249
    }
250
}
251