1 | <?php |
||||
2 | namespace HakimCh\Http; |
||||
3 | |||||
4 | class RouterParser implements RouterParserInterface |
||||
5 | { |
||||
6 | protected $params = []; |
||||
7 | protected $matchTypes = [ |
||||
8 | 'i' => '[0-9]++', |
||||
9 | 'a' => '[0-9A-Za-z]++', |
||||
10 | 'h' => '[0-9A-Fa-f]++', |
||||
11 | '*' => '.+?', |
||||
12 | '**' => '.++', |
||||
13 | '' => '[^/\.]++' |
||||
14 | ]; |
||||
15 | |||||
16 | /** |
||||
17 | * Create router in one call from config. |
||||
18 | * |
||||
19 | * @param array $matchTypes |
||||
20 | */ |
||||
21 | public function __construct($matchTypes = []) |
||||
22 | { |
||||
23 | $this->setMatchTypes($matchTypes); |
||||
24 | } |
||||
25 | |||||
26 | /** |
||||
27 | * Add named match types. It uses array_merge so keys can be overwritten. |
||||
28 | * |
||||
29 | * @param array $matchTypes The key is the name and the value is the regex. |
||||
30 | */ |
||||
31 | public function setMatchTypes($matchTypes) |
||||
32 | { |
||||
33 | $this->matchTypes = array_merge($this->matchTypes, $matchTypes); |
||||
34 | } |
||||
35 | |||||
36 | /** |
||||
37 | * Get the url from a route name |
||||
38 | * |
||||
39 | * @param string $basePath |
||||
40 | * @param string $route |
||||
41 | * @param array $params |
||||
42 | * |
||||
43 | * @return string |
||||
44 | */ |
||||
45 | public function generate($basePath, $route, array $params) |
||||
46 | { |
||||
47 | $url = $basePath . $route; |
||||
48 | |||||
49 | if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { |
||||
50 | foreach ($matches as $match) { |
||||
51 | $block = $match[0]; |
||||
52 | $pre = $match[1]; |
||||
53 | $param = $match[3]; |
||||
54 | |||||
55 | if ($pre) { |
||||
56 | $block = substr($block, 1); |
||||
57 | } |
||||
58 | |||||
59 | if (isset($params[$param])) { |
||||
60 | $url = str_replace($block, $params[$param], $url); |
||||
61 | } elseif ($match[4]) { |
||||
62 | $url = str_replace($pre . $block, '', $url); |
||||
63 | } |
||||
64 | } |
||||
65 | } |
||||
66 | |||||
67 | return $url; |
||||
68 | } |
||||
69 | |||||
70 | /** |
||||
71 | * @param $method |
||||
72 | * @param $requestMethod |
||||
73 | * @param string|array $routeString |
||||
74 | * @param $requestUrl |
||||
75 | * |
||||
76 | * @return mixed |
||||
77 | */ |
||||
78 | public function methodMatch($method, $requestMethod, $routeString, $requestUrl) |
||||
79 | { |
||||
80 | $method = strtolower($method); |
||||
81 | $requestMethod = strtolower($requestMethod); |
||||
82 | $methods = explode('|', $method); |
||||
83 | |||||
84 | if(in_array($requestMethod, $methods)) { |
||||
85 | if($routeString == '*') { |
||||
86 | return true; |
||||
87 | } |
||||
88 | elseif(isset($routeString[0]) && $routeString[0] == '@') { |
||||
89 | $match = preg_match('`' . substr($routeString, 1) . '`u', $requestUrl, $this->params); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
90 | return $match; |
||||
91 | } |
||||
92 | elseif (($position = strpos($routeString, '[')) === false) { |
||||
0 ignored issues
–
show
It seems like
$routeString can also be of type array ; however, parameter $haystack of strpos() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
93 | return strcmp($requestUrl, $routeString) === 0; |
||||
94 | } |
||||
95 | else { |
||||
96 | if (strncmp($requestUrl, $routeString, $position) !== 0) { |
||||
0 ignored issues
–
show
It seems like
$routeString can also be of type array ; however, parameter $str2 of strncmp() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
97 | return false; |
||||
98 | } |
||||
99 | $regex = $this->compileRoute($routeString, $requestUrl); |
||||
100 | return preg_match($regex, $requestUrl, $this->params); |
||||
101 | } |
||||
102 | } |
||||
103 | |||||
104 | return false; |
||||
105 | } |
||||
106 | |||||
107 | /** |
||||
108 | * Compile the regex for a given route (EXPENSIVE) |
||||
109 | */ |
||||
110 | private function compileRoute($routeString, $requestUrl) |
||||
111 | { |
||||
112 | $route = $this->getRoute($routeString, $requestUrl); |
||||
113 | |||||
114 | if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { |
||||
115 | $matchTypes = $this->matchTypes; |
||||
116 | foreach ($matches as $match) { |
||||
117 | list($block, $pre, $type, $param, $optional) = $match; |
||||
118 | $pattern = $this->getRoutePattern($matchTypes, $pre, $type, $param, $optional); |
||||
119 | $route = str_replace($block, $pattern, $route); |
||||
120 | } |
||||
121 | } |
||||
122 | |||||
123 | return "`^$route$`u"; |
||||
124 | } |
||||
125 | |||||
126 | private function getRoutePattern($matchTypes, $pre, $type, $param, $optional) |
||||
127 | { |
||||
128 | if (isset($matchTypes[$type])) { |
||||
129 | $type = $matchTypes[$type]; |
||||
130 | } |
||||
131 | if ($pre === '.') { |
||||
132 | $pre = '\.'; |
||||
133 | } |
||||
134 | |||||
135 | //Older versions of PCRE require the 'P' in (?P<named>) |
||||
136 | return '(?:' |
||||
137 | . ($pre !== '' ? $pre : null) |
||||
138 | . '(' |
||||
139 | . ($param !== '' ? "?P<$param>" : null) |
||||
140 | . $type |
||||
141 | . '))' |
||||
142 | . ($optional !== '' ? '?' : null); |
||||
143 | } |
||||
144 | |||||
145 | /** |
||||
146 | * @param $routeString |
||||
147 | * @param $requestUrl |
||||
148 | * |
||||
149 | * @return bool|string |
||||
150 | */ |
||||
151 | private function getRoute($routeString, $requestUrl) |
||||
152 | { |
||||
153 | $iPointer = $jPointer = 0; |
||||
154 | $nPointer = isset($routeString[0]) ? $routeString[0] : null; |
||||
155 | $regex = $route = false; |
||||
156 | |||||
157 | // Find the longest non-regex substring and match it against the URI |
||||
158 | while (true) { |
||||
159 | if (!isset($routeString[$iPointer])) { |
||||
160 | break; |
||||
161 | } |
||||
162 | if ($regex === false) { |
||||
163 | if(!$this->getRouteRegexCheck($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)) { |
||||
164 | continue; |
||||
165 | } |
||||
166 | $jPointer++; |
||||
167 | } |
||||
168 | $route .= $routeString[$iPointer++]; |
||||
169 | } |
||||
170 | |||||
171 | return $route; |
||||
172 | } |
||||
173 | |||||
174 | private function getRouteRegexCheck($nPointer, $jPointer, $iPointer, $routeString, $requestUrl) |
||||
175 | { |
||||
176 | $cPointer = $nPointer; |
||||
177 | $regex = in_array($cPointer, array('[', '(', '.')); |
||||
178 | if (!$regex && isset($routeString[$iPointer+1])) { |
||||
179 | $nPointer = $routeString[$iPointer + 1]; |
||||
180 | $regex = in_array($nPointer, array('?', '+', '*', '{')); |
||||
181 | } |
||||
182 | if (!$regex && $cPointer !== '/' && (!isset($requestUrl[$jPointer]) || $cPointer !== $requestUrl[$jPointer])) { |
||||
183 | return false; |
||||
184 | } |
||||
185 | return true; |
||||
186 | } |
||||
187 | |||||
188 | /** |
||||
189 | * @return array |
||||
190 | */ |
||||
191 | public function getParams() |
||||
192 | { |
||||
193 | return $this->params; |
||||
194 | } |
||||
195 | |||||
196 | public function getMatchTypes() |
||||
197 | { |
||||
198 | return $this->matchTypes; |
||||
199 | } |
||||
200 | } |
||||
201 |