Passed
Pull Request — 3.x (#208)
by
unknown
01:58
created

Matcher::getMatchedTree()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 8
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 20
rs 10
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\Router;
10
11
use Aura\Router\Rule\RuleIterator;
12
use Psr\Http\Message\ServerRequestInterface;
13
use Psr\Log\LoggerInterface;
14
15
/**
16
 *
17
 * Matches against the route map.
18
 *
19
 * @package Aura.Router
20
 *
21
 */
22
class Matcher
23
{
24
    /**
25
     *
26
     * Logging information about which routes were attempted to match.
27
     *
28
     * @var LoggerInterface
29
     *
30
     */
31
    protected $logger;
32
33
    /**
34
     *
35
     * The map of all routes.
36
     *
37
     * @var Map
38
     *
39
     */
40
    protected $map;
41
42
    /**
43
     *
44
     * A collection of matching rules to iterate through.
45
     *
46
     * @var RuleIterator
47
     *
48
     */
49
    protected $ruleIterator;
50
51
    /**
52
     *
53
     * The Route object matched by the router.
54
     *
55
     * @var Route|false|null
56
     *
57
     */
58
    protected $matchedRoute;
59
60
    /**
61
     *
62
     * The first of the closest-matching failed routes.
63
     *
64
     * @var Route|null
65
     *
66
     */
67
    protected $failedRoute;
68
69
    /**
70
     *
71
     * The score of the closest-matching failed route.
72
     *
73
     * @var int
74
     *
75
     */
76
    protected $failedScore = 0;
77
78
    /**
79
     *
80
     * Constructor.
81
     *
82
     * @param Map $map A route collection object.
83
     *
84
     * @param LoggerInterface $logger A logger object.
85
     *
86
     * @param RuleIterator $ruleIterator A collection of matching rules.
87
     *
88
     */
89
    public function __construct(
90 5
        Map $map,
91
        LoggerInterface $logger,
92
        RuleIterator $ruleIterator
93
    ) {
94
        $this->map = $map;
95 5
        $this->logger = $logger;
96 5
        $this->ruleIterator = $ruleIterator;
97 5
    }
98 5
99
    /**
100
     *
101
     * Gets a route that matches the request.
102
     *
103
     * @param ServerRequestInterface $request The incoming request.
104
     *
105
     * @return Route|false Returns a route object when it finds a match, or
106
     * boolean false if there is no match.
107
     *
108
     */
109
    public function match(ServerRequestInterface $request)
110 5
    {
111
        $this->matchedRoute = false;
112 5
        $this->failedRoute = null;
113 5
        $this->failedScore = 0;
114 5
        $path = $request->getUri()->getPath();
115 5
116
        $possibleRoutes = $this->getMatchedTree($path);
117 5
        foreach ($possibleRoutes as $proto) {
118 5
            if (is_array($proto)) {
119 5
                continue;
120 5
            }
121
            $route = $this->requestRoute($request, $proto, $path);
122
            if ($route) {
123
                return $route;
124 3
            }
125
        }
126
127
        return false;
128
    }
129
130
    /**
131
     *
132
     * Match a request to a route.
133
     *
134
     * @param ServerRequestInterface $request The request to match against.
135
     *
136
     * @param Route $proto The proto-route to match against.
137
     *
138
     * @param string $path The request path.
139
     *
140
     * @return mixed False on failure, or a Route on match.
141
     *
142 5
     */
143
    protected function requestRoute($request, $proto, $path)
144 5
    {
145 1
        if (! $proto->isRoutable) {
146
            return false;
147 5
        }
148 5
        $route = clone $proto;
149
        return $this->applyRules($request, $route, $route->name, $path);
150
    }
151
152
    /**
153
     *
154
     * Does the request match a route per the matching rules?
155
     *
156
     * @param ServerRequestInterface $request The request to match against.
157
     *
158
     * @param Route $route The route to match against.
159
     *
160
     * @param string $name The route name.
161
     *
162
     * @param string $path The request path.
163
     *
164
     * @return mixed False on failure, or a Route on match.
165
     *
166 5
     */
167
    protected function applyRules($request, $route, $name, $path)
168 5
    {
169 5
        $score = 0;
170 5
        foreach ($this->ruleIterator as $rule) {
171 4
            if (! $rule($request, $route)) {
172
                return $this->ruleFailed($request, $route, $name, $path, $rule, $score);
173 5
            }
174
            $score ++;
175 3
        }
176
        return $this->routeMatched($route, $name, $path);
177
    }
178
179
    /**
180
     *
181
     * A matching rule failed.
182
     *
183
     * @param ServerRequestInterface $request The request to match against.
184
     *
185
     * @param Route $route The route to match against.
186
     *
187
     * @param string $name The route name.
188
     *
189
     * @param string $path The request path.
190
     *
191
     * @param mixed $rule The rule that failed.
192
     *
193
     * @param int $score The failure score.
194
     *
195
     * @return false
196
     *
197 4
     */
198
    protected function ruleFailed($request, $route, $name, $path, $rule, $score)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

198
    protected function ruleFailed(/** @scrutinizer ignore-unused */ $request, $route, $name, $path, $rule, $score)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
199 4
    {
200 4
        $ruleClass = get_class($rule);
201
        $route->failedRule($ruleClass);
202 4
203 4
        if (! $this->failedRoute || $score > $this->failedScore) {
204 4
            $this->failedRoute = $route;
205
            $this->failedScore = $score;
206
        }
207 4
208 4
        $this->logger->debug("{path} FAILED {ruleClass} ON {name}", [
209 4
            'path' => $path,
210 4
            'ruleClass' => $ruleClass,
211
            'name' => $name
212
        ]);
213 4
214
        return false;
215
    }
216
217
    /**
218
     *
219
     * The route matched.
220
     *
221
     * @param Route $route The route to match against.
222
     *
223
     * @param string $name The route name.
224
     *
225
     * @param string $path The request path.
226
     *
227
     * @return Route
228
     *
229 3
     */
230
    protected function routeMatched($route, $name, $path)
231 3
    {
232 3
        $this->logger->debug("{path} MATCHED ON {name}", [
233 3
            'path' => $path,
234
            'name' => $name,
235 3
        ]);
236 3
        $this->matchedRoute = $route;
237
        return $route;
238
    }
239
240
    /**
241
     *
242
     * Get the first of the closest-matching failed routes.
243
     *
244
     * @return ?Route
245
     *
246 2
     */
247
    public function getFailedRoute()
248 2
    {
249
        return $this->failedRoute;
250
    }
251
252
    /**
253
     *
254
     * Returns the result of the call to match() again so you don't need to
255
     * run the matching process again.
256
     *
257
     * @return Route|false|null Returns null if match() has not been called
258
     * yet, false if it has and there was no match, or a Route object if there
259
     * was a match.
260
     *
261 3
     */
262
    public function getMatchedRoute()
263 3
    {
264
        return $this->matchedRoute;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->matchedRoute also could return the type boolean which is incompatible with the documented return type Aura\Router\Route|false|null.
Loading history...
265
    }
266
267
    /**
268
     * Split the URL into URL Segments and check for matching routes per segment
269
     * This segment could return a list of possible routes
270
     *
271
     * @param string $path
272
     * @return \RecursiveArrayIterator
273
     */
274
    private function getMatchedTree($path)
275
    {
276
        $node = $this->map->getAsTreeRouteNode();
277
        foreach (explode('/', trim($path, '/')) as $segment) {
278
            if (isset($node[$segment])) {
279
                $node = $node[$segment];
280
                continue;
281
            }
282
            if (isset($node['{}'])) {
283
                $node = $node['{}'];
284
            }
285
        }
286
287
        return new \RecursiveArrayIterator($node);
288
    }
289
}
290