Passed
Push — coderabbitai/docstrings/e4Aw ( f1c010...f38670 )
by
unknown
04:08 queued 02:24
created

Matcher   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 2
Metric Value
eloc 58
c 9
b 0
f 2
dl 0
loc 255
rs 10
wmc 20

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getFailedRoute() 0 3 1
A routeMatched() 0 8 1
A requestRoute() 0 7 2
A getMatchedRoute() 0 3 1
A getMatchedTree() 0 14 4
A applyRules() 0 10 3
A ruleFailed() 0 17 3
A match() 0 19 4
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
        Map $map,
91
        LoggerInterface $logger,
92
        RuleIterator $ruleIterator
93
    ) {
94
        $this->map = $map;
95
        $this->logger = $logger;
96
        $this->ruleIterator = $ruleIterator;
97
    }
98
99
    /****
100
     * Attempts to find and return the first route that matches the given HTTP request.
101
     *
102
     * Filters candidate routes based on the request path, then applies matching rules to each candidate until a match is found. Returns the matched route or false if no route matches.
103
     *
104
     * @param ServerRequestInterface $request The HTTP request to match against available routes.
105
     * @return Route|false The matched route object, or false if no route matches the request.
106
     */
107
    public function match(ServerRequestInterface $request)
108
    {
109
        $this->matchedRoute = false;
110
        $this->failedRoute = null;
111
        $this->failedScore = 0;
112
        $path = $request->getUri()->getPath();
113
114
        $possibleRoutes = $this->getMatchedTree($path);
115
        foreach ($possibleRoutes as $proto) {
116
            if (is_array($proto)) {
117
                continue;
118
            }
119
            $route = $this->requestRoute($request, $proto, $path);
120
            if ($route) {
121
                return $route;
122
            }
123
        }
124
125
        return false;
126
    }
127
128
    /****
129
     * Attempts to match a proto-route to the given request and path.
130
     *
131
     * Clones the provided proto-route and applies matching rules to determine if it matches the request and path.
132
     *
133
     * @param ServerRequestInterface $request The HTTP request to match.
134
     * @param Route $proto The proto-route candidate.
135
     * @param string $path The request path.
136
     * @return Route|false The matched Route on success, or false if the proto-route is not routable or does not match.
137
     */
138
    protected function requestRoute($request, $proto, $path)
139
    {
140
        if (! $proto->isRoutable) {
141
            return false;
142
        }
143
        $route = clone $proto;
144
        return $this->applyRules($request, $route, $route->name, $path);
145
    }
146
147
    /**
148
     *
149
     * Does the request match a route per the matching rules?
150
     *
151
     * @param ServerRequestInterface $request The request to match against.
152
     *
153
     * @param Route $route The route to match against.
154
     *
155
     * @param string $name The route name.
156
     *
157
     * @param string $path The request path.
158
     *
159
     * @return mixed False on failure, or a Route on match.
160
     *
161
     */
162
    protected function applyRules($request, $route, $name, $path)
163
    {
164
        $score = 0;
165
        foreach ($this->ruleIterator as $rule) {
166
            if (! $rule($request, $route)) {
167
                return $this->ruleFailed($request, $route, $name, $path, $rule, $score);
168
            }
169
            $score ++;
170
        }
171
        return $this->routeMatched($route, $name, $path);
172
    }
173
174
    /**
175
     *
176
     * A matching rule failed.
177
     *
178
     * @param ServerRequestInterface $request The request to match against.
179
     *
180
     * @param Route $route The route to match against.
181
     *
182
     * @param string $name The route name.
183
     *
184
     * @param string $path The request path.
185
     *
186
     * @param mixed $rule The rule that failed.
187
     *
188
     * @param int $score The failure score.
189
     *
190
     * @return false
191
     *
192
     */
193
    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

193
    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...
194
    {
195
        $ruleClass = get_class($rule);
196
        $route->failedRule($ruleClass);
197
198
        if (! $this->failedRoute || $score > $this->failedScore) {
199
            $this->failedRoute = $route;
200
            $this->failedScore = $score;
201
        }
202
203
        $this->logger->debug("{path} FAILED {ruleClass} ON {name}", [
204
            'path' => $path,
205
            'ruleClass' => $ruleClass,
206
            'name' => $name
207
        ]);
208
209
        return false;
210
    }
211
212
    /**
213
     *
214
     * The route matched.
215
     *
216
     * @param Route $route The route to match against.
217
     *
218
     * @param string $name The route name.
219
     *
220
     * @param string $path The request path.
221
     *
222
     * @return Route
223
     *
224
     */
225
    protected function routeMatched($route, $name, $path)
226
    {
227
        $this->logger->debug("{path} MATCHED ON {name}", [
228
            'path' => $path,
229
            'name' => $name,
230
        ]);
231
        $this->matchedRoute = $route;
232
        return $route;
233
    }
234
235
    /**
236
     *
237
     * Get the first of the closest-matching failed routes.
238
     *
239
     * @return ?Route
240
     *
241
     */
242
    public function getFailedRoute()
243
    {
244
        return $this->failedRoute;
245
    }
246
247
    /****
248
     * Retrieves the result of the most recent route matching attempt.
249
     *
250
     * @return Route|false|null The matched Route object, false if no route matched, or null if matching has not been attempted.
251
     */
252
    public function getMatchedRoute()
253
    {
254
        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...
255
    }
256
257
    /****
258
     * Traverses the route map tree according to the given URL path segments and returns an iterator over the matching subtree of routes.
259
     *
260
     * @param string $path The URL path to match, e.g., "/users/123".
261
     * @return \RecursiveArrayIterator Iterator over the subtree of routes matching the path segments.
262
     */
263
    private function getMatchedTree($path)
264
    {
265
        $node = $this->map->getAsTreeRouteNode();
266
        foreach (explode('/', trim($path, '/')) as $segment) {
267
            if (isset($node[$segment])) {
268
                $node = $node[$segment];
269
                continue;
270
            }
271
            if (isset($node['{}'])) {
272
                $node = $node['{}'];
273
            }
274
        }
275
276
        return new \RecursiveArrayIterator($node);
277
    }
278
}
279