Test Failed
Push — master ( c0baca...92e477 )
by Bohuslav
10:33
created

Matcher::parseRoutes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Kambo\Router;
4
5
// \spl
6
use InvalidArgumentException;
7
8
// \Psr\Http\Message
9
use Psr\Http\Message\ServerRequestInterface as ServerRequest;
10
11
// \Kambo\Router\
12
use Kambo\Router\Dispatchers\Interfaces\DispatcherInterface;
13
use Kambo\Router\Route\Collection;
14
use Kambo\Router\Route\ParsedRoute;
15
16
// \Kambo\Router\Enum
17
use Kambo\Router\Enum\Method;
18
use Kambo\Router\Enum\RouteMode;
19
20
/**
21
 * Match provided request object with all defined routes in route collection.
22
 * If some of routes match a data in provided request An instace of matched
23
 * route is returned. If nothing is matched false value is returned.
24
 *
25
 * @author  Bohuslav Simek <[email protected]>
26
 * @license Apache-2.0
27
 * @package Kambo\Router
28
 */
29
class Matcher
30
{
31
    /**
32
     * Regex for getting URL variables
33
     *
34
     * @var string
35
     */
36
    const VARIABLE_REGEX =
37
        "~\{
38
            \s* ([a-zA-Z0-9_]*) \s*
39
            (?:
40
                : \s* ([^{]+(?:\{.*?\})?)
41
            )?
42
        \}\??~x";
43
44
    /**
45
     * Shortcuts for regex.
46
     *
47
     * @var array
48
     */
49
    private $regexShortcuts = [
50
        ':i}'  => ':[0-9]+}',
51
        ':a}'  => ':[0-9A-Za-z]+}',
52
        ':h}'  => ':[0-9A-Fa-f]+}',
53
        ':c}'  => ':[a-zA-Z0-9+_\-\.]+}'
54
    ];
55
56
    /**
57
     * Flag for enabling support of mode rewrite.
58
     *
59
     * @var string
60
     */
61
    private $urlFormat = RouteMode::PATH_FORMAT;
62
63
    /**
64
     * Name of GET parameter from which the route will be get
65
     * if url format is set to GET_FORMAT.
66
     *
67
     * @var string
68
     */
69
    private $modeRewriteParameter = 'r';
70
71
    /**
72
     * Instance of route collection
73
     *
74
     * @var \Kambo\Router\Route\Collection
75
     */
76
    private $routeCollection;
77
78
    /**
79
     * Constructor
80
     *
81
     * @param \Kambo\Router\Route\Collection                           $routeCollection
82
     * @param \Kambo\Router\Dispatchers\Interfaces\DispatcherInterface $dispatcher
0 ignored issues
show
Bug introduced by
There is no parameter named $dispatcher. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
83
     *
84
     */
85
    public function __construct(
86
        Collection $routeCollection
87
    ) {
88
        $this->routeCollection = $routeCollection;
89
    }
90 22
91
    /**
92
     * Match request with provided routes.
93
     * Get method and url from provided request and start matching.
94 22
     *
95 22
     * @param ServerRequest $request instance of PSR 7 compatible request object
96 22
     *
97
     * @return mixed
98
     */
99
    public function matchRequest(/*ServerRequest */ $request)
100
    {
101
        return $this->getMatchRoute(
102
            $request->getMethod(),
103
            $this->getUrl($request)
104
        );
105
    }
106 3
107
    /**
108 3
     * Match url and method with provided routes.
109 3
     *
110 3
     * @param string $method http method
111 3
     * @param string $url    url
112
     *
113
     * @return mixed
114
     */
115
    public function matchPathAndMethod($method, $url)
116
    {
117
        return $this->getMatchRoute($method, $url);
118
    }
119
120
    /**
121
     * Match method and route with provided routes.
122
     * If route and method match a route is dispatch using provided dispatcher.
123 20
     *
124
     * @param string $method http method
125 20
     * @param string $url    url
126 20
     *
127 18
     * @return mixed
128 18
     */
129 18
    private function getMatchRoute($method, $url)
130 16
    {
131 16
        $parsedRoutes = $this->parseRoutes($this->routeCollection);
132 16
        foreach ($parsedRoutes as $singleParsedRoute) {
133 16
            list($routeRegex, $route) = $singleParsedRoute;
134
            $matchedParameters = $this->routeMatch($routeRegex, $url);
135 1
            if ($matchedParameters !== false) {
136 5
                $routeMethod = $route->getMethod();
137
                if ($routeMethod === $method || $routeMethod === Method::ANY) {
138 4
                    $route->setParameters($matchedParameters);
139
140
                    return $route;
141
                }
142
            }
143
        }
144
145
        return false;
146
    }
147
148
    /**
149
     * Set format for URL resolving.
150 4
     * If the path mode is set to a path a web server must be properly
151
     * configurated, defualt value is PATH_FORMAT.
152 4
     *
153 3
     * @param string $urlFormat value from RouteMode enum
154 3
     *
155 1
     * @return self for fluent interface
156
     */
157 1
    public function setUrlFormat($urlFormat)
158
    {
159
        if (RouteMode::inEnum($urlFormat)) {
160 3
            $this->urlFormat = $urlFormat;
161
        } else {
162
            throw new InvalidArgumentException(
163
                'Value of urlFormat must be from RouteMode enum.'
164
            );
165
        }
166
167
        return $this;
168 1
    }
169
170 1
    /**
171
     * Get format for URL resolving.
172
     *
173
     * @return string value from RouteMode enum
174
     */
175
    public function getUrlFormat()
176
    {
177
        return $this->urlFormat;
178
    }
179
180
    // ------------ PRIVATE METHODS
181
182
    /**
183 18
     * Match route by provided regex.
184
     *
185 18
     * @param string $routeRegex
186 18
     * @param string $route
187 16
     *
188
     * @return mixed
189 16
     */
190
    private function routeMatch($routeRegex, $route)
191
    {
192 2
        $matches = [];
193
        if (preg_match($routeRegex, $route, $matches)) {
194
            unset($matches[0]);
195
196
            return array_values($matches);
197
        }
198
199
        return false;
200
    }
201
202 20
    /**
203
     * Prepare regex and parameters for each of routes.
204 20
     *
205 20
     * @param array $routes array with instances of route object
206 18
     *
207
     * @return array transformed routes
208 18
     */
209
    private function parseRoutes(Collection $routes)
210 18
    {
211 18
        $parsedRoutes = [];
212
        foreach ($routes as $route) {
213 18
            $routeUrl = strtr($route->getUrl(), $this->regexShortcuts);
214 20
215
            list($routeRegex, $parameters) = $this->transformRoute($routeUrl);
216 20
217
            $parsedRoute = new ParsedRoute($route);
218
            $parsedRoute->setPlaceholders($parameters);
219
220
            $parsedRoutes[] = [$routeRegex, $parsedRoute];
221
        }
222
223
        return $parsedRoutes;
224
    }
225
226
    /**
227 3
     * Get route from request object.
228
     * Method expect an instance of PSR 7 compatible request object.
229 3
     *
230 1
     * @param object $request
231 1
     *
232 2
     * @return string
233 2
     */
234 2
    private function getUrl($request)
235 2
    {
236 2
        if ($this->urlFormat === RouteMode::PATH_FORMAT) {
237
            $path = $request->getUri()->getPath();
238 2
        } else {
239
            $queryParams = $request->getQueryParams();
240
            $route       = null;
241 3
            if (isset($queryParams[$this->modeRewriteParameter])) {
242
                $route = $queryParams[$this->modeRewriteParameter];
243
            }
244
245
            $path = '/'.$route;
246
        }
247
248
        return $path;
249
    }
250
251 18
    /**
252
     * Prepare regex for resolving route a extract variables from route.
253 18
     *
254 18
     * @param string $route
255 18
     *
256 14
     * @return array regex and parameters
257 14
     */
258 14
    private function transformRoute($route)
259
    {
260 14
        $parameters = $this->extractVariableRouteParts($route);
261 14
        if (isset($parameters)) {
262 14
            foreach ($parameters as $variables) {
263 14
                list($valueToReplace, , $parametersVariables) = array_pad(
264 14
                    $variables,
265
                    3,
266 14
                    null
267 14
                );
268 12
                if (isset($parametersVariables)) {
269
                    $route = str_replace(
270 18
                        $valueToReplace,
271 18
                        '('.reset($parametersVariables).')',
272
                        $route
273 18
                    );
274
                } else {
275 18
                    $route = str_replace($valueToReplace, '([^/]+)', $route);
276
                }
277
            }
278
        }
279
280
        $route = '~^'.$route.'$~';
281
282
        return [$route, $parameters];
283
    }
284
285 18
    /**
286
     * Extract variables from route
287 18
     *
288 18
     * @param string $route
289 18
     *
290 18
     * @return null|array
291 18
     */
292 18
    private function extractVariableRouteParts($route)
293 18
    {
294
        $matches = null;
295 18
        preg_match_all(
296
            self::VARIABLE_REGEX,
297
            $route,
298
            $matches,
299
            PREG_OFFSET_CAPTURE | PREG_SET_ORDER
300
        );
301
302
        return $matches;
303
    }
304
}
305