1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
<?php
declare(strict_types=1);
namespace BrettParson\MiniRoute\Routing;
final class RouteMatcher
{
public function matches(RouteDef $route, string $method, string $path): bool
{
if ($route->method !== strtoupper($method)) {
return false;
}
return preg_match($this->compile($route->path), $path) === 1;
}
/**
* Extract named route parameters from a matched path.
*
* @return array<string, string>
*/
public function extractParams(RouteDef $route, string $path): array
{
if (preg_match($this->compile($route->path), $path, $matches) !== 1) {
return [];
}
$params = [];
foreach ($matches as $key => $value) {
if (is_string($key)) {
$params[$key] = $value;
}
}
return $params;
}
/**
* Order two routes so the more specific one sorts first.
*
* Segments are compared left to right; a literal segment beats a
* {parameter} segment at the same position. So /entries/archive sorts
* before /entries/{slug} and /admin/entries/preview before
* /admin/entries/{id}, independent of registration order.
*/
public function compare(RouteDef $a, RouteDef $b): int
{
$segmentsA = $this->segments($a->path);
$segmentsB = $this->segments($b->path);
$length = max(count($segmentsA), count($segmentsB));
for ($i = 0; $i < $length; $i++) {
$rankA = $this->rank($segmentsA[$i] ?? null);
$rankB = $this->rank($segmentsB[$i] ?? null);
if ($rankA !== $rankB) {
return $rankA <=> $rankB;
}
}
return 0;
}
private function compile(string $path): string
{
$escaped = preg_quote($path, '#');
$pattern = preg_replace(
'#\\\\\{([a-zA-Z_]+)\\\\\}#',
'(?P<$1>[^/]+)',
$escaped,
);
return '#^' . $pattern . '$#';
}
/**
* @return list<string>
*/
private function segments(string $path): array
{
$trimmed = trim($path, '/');
if ($trimmed === '') {
return [];
}
return explode('/', $trimmed);
}
/**
* Lower rank = more specific: literal (0) < parameter (1) < missing (2).
*/
private function rank(?string $segment): int
{
if ($segment === null) {
return 2;
}
if (str_starts_with($segment, '{') && str_ends_with($segment, '}')) {
return 1;
}
return 0;
}
}
|