1 | <?php |
||
2 | namespace Mezon\Router; |
||
3 | |||
4 | class UrlParser |
||
5 | { |
||
6 | |||
7 | /** |
||
8 | * Supported types of URL parameters |
||
9 | * |
||
10 | * @var array |
||
11 | */ |
||
12 | private $types = []; |
||
13 | |||
14 | /** |
||
15 | * Parsed parameters of the calling router |
||
16 | * |
||
17 | * @var array |
||
18 | */ |
||
19 | protected $parameters = []; |
||
20 | |||
21 | /** |
||
22 | * Method handles integer type |
||
23 | * |
||
24 | * @param string $value |
||
25 | * value to be parsed |
||
26 | * @return bool was the value parsed |
||
27 | */ |
||
28 | public static function intHandler(string &$value): bool |
||
29 | { |
||
30 | if (is_numeric($value)) { |
||
31 | $value = $value + 0; |
||
32 | return true; |
||
33 | } |
||
34 | |||
35 | return false; |
||
36 | } |
||
37 | |||
38 | /** |
||
39 | * Method handles command type |
||
40 | * |
||
41 | * @param string $value |
||
42 | * value to be parsed |
||
43 | * @return bool was the value parsed |
||
44 | */ |
||
45 | public static function commandHandler(string &$value): bool |
||
46 | { |
||
47 | if (preg_match('/^([a-z0-9A-Z_\/\-\.\@]+)$/', $value)) { |
||
48 | return true; |
||
49 | } |
||
50 | |||
51 | return false; |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Method handles list of integers type |
||
56 | * |
||
57 | * @param string $value |
||
58 | * value to be parsed |
||
59 | * @return bool was the value parsed |
||
60 | */ |
||
61 | public static function intListHandler(string &$value): bool |
||
62 | { |
||
63 | if (preg_match('/^([0-9,]+)$/', $value)) { |
||
64 | return true; |
||
65 | } |
||
66 | |||
67 | return false; |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Method handles string type |
||
72 | * |
||
73 | * @param string $value |
||
74 | * value to be parsed |
||
75 | * @return bool was the value parsed |
||
76 | */ |
||
77 | public static function stringHandler(string &$value): bool |
||
78 | { |
||
79 | $value = htmlspecialchars($value, ENT_QUOTES); |
||
80 | |||
81 | return true; |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Constructor |
||
86 | */ |
||
87 | public function __construct() |
||
88 | { |
||
89 | $this->types['i'] = '\Mezon\Router\UrlParser::intHandler'; |
||
90 | $this->types['a'] = '\Mezon\Router\UrlParser::commandHandler'; |
||
91 | $this->types['il'] = '\Mezon\Router\UrlParser::intListHandler'; |
||
92 | $this->types['s'] = '\Mezon\Router\UrlParser::stringHandler'; |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Matching parameter and component |
||
97 | * |
||
98 | * @param mixed $component |
||
99 | * Component of the URL |
||
100 | * @param string $parameter |
||
101 | * Parameter to be matched |
||
102 | * @return string Matched url parameter |
||
103 | */ |
||
104 | private function _matchParameterAndComponent(&$component, string $parameter) |
||
105 | { |
||
106 | $parameterData = explode(':', trim($parameter, '[]')); |
||
107 | $return = ''; |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
108 | |||
109 | if (isset($this->types[$parameterData[0]])) { |
||
110 | if ($this->types[$parameterData[0]]($component)) { |
||
111 | return $parameterData[1]; |
||
112 | } else { |
||
113 | return ''; |
||
114 | } |
||
115 | } else { |
||
116 | throw (new \Exception('Unknown parameter type : ' . $parameterData[0])); |
||
117 | } |
||
118 | |||
119 | return $return; |
||
0 ignored issues
–
show
return $return is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Method matches route and pattern |
||
124 | * |
||
125 | * @param array $cleanRoute |
||
126 | * Cleaned route splitted in parts |
||
127 | * @param array $cleanPattern |
||
128 | * Route pattern |
||
129 | * @return array|bool Array of route's parameters |
||
130 | */ |
||
131 | private function _matchRouteAndPattern(array $cleanRoute, array $cleanPattern) |
||
132 | { |
||
133 | if (count($cleanRoute) !== count($cleanPattern)) { |
||
134 | return false; |
||
135 | } |
||
136 | |||
137 | $paremeters = []; |
||
138 | $patternsCount = count($cleanPattern); |
||
139 | |||
140 | for ($i = 0; $i < $patternsCount; $i ++) { |
||
141 | if (\Mezon\Router\Utils::isParameter($cleanPattern[$i])) { |
||
142 | $parameterName = $this->_matchParameterAndComponent($cleanRoute[$i], $cleanPattern[$i]); |
||
143 | |||
144 | // it's a parameter |
||
145 | if ($parameterName !== '') { |
||
146 | // parameter was matched, store it! |
||
147 | $paremeters[$parameterName] = $cleanRoute[$i]; |
||
148 | } else { |
||
149 | return false; |
||
150 | } |
||
151 | } else { |
||
152 | // it's a static part of the route |
||
153 | if ($cleanRoute[$i] !== $cleanPattern[$i]) { |
||
154 | return false; |
||
155 | } |
||
156 | } |
||
157 | } |
||
158 | |||
159 | $this->parameters = $paremeters; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Method searches dynamic route processor |
||
164 | * |
||
165 | * @param array $processors |
||
166 | * Callable router's processor |
||
167 | * @param string $route |
||
168 | * Route |
||
169 | * @return string|bool Result of the router'scall or false if any error occured |
||
170 | */ |
||
171 | public function findDynamicRouteProcessor(array &$processors, string $route) |
||
172 | { |
||
173 | $cleanRoute = explode('/', trim($route, '/')); |
||
174 | |||
175 | foreach ($processors as $i => $processor) { |
||
176 | $cleanPattern = explode('/', trim($i, '/')); |
||
177 | |||
178 | if ($this->_matchRouteAndPattern($cleanRoute, $cleanPattern) !== false) { |
||
179 | return call_user_func($processor, $route, $this->parameters); // return result of the router |
||
180 | } |
||
181 | } |
||
182 | |||
183 | return false; |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Checking that method exists |
||
188 | * |
||
189 | * @param object|array $processor |
||
190 | * callback object |
||
191 | * @param ?string $functionName |
||
192 | * callback method |
||
193 | * @return bool true if method does not exists |
||
194 | */ |
||
195 | private function methodDoesNotExists($processor, ?string $functionName): bool |
||
196 | { |
||
197 | return isset($processor[0]) && method_exists($processor[0], $functionName) === false; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Checking that handler can be called |
||
202 | * |
||
203 | * @param object|array $processor |
||
204 | * callback object |
||
205 | * @param ?string $functionName |
||
206 | * callback method |
||
207 | * @return bool |
||
208 | */ |
||
209 | private function canBeCalled($processor, ?string $functionName): bool |
||
210 | { |
||
211 | return is_callable($processor) && |
||
212 | (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName)); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * Checking that processor can be called as function |
||
217 | * |
||
218 | * @param mixed $processor |
||
219 | * route processor |
||
220 | * @return bool true if the $processor can be called as function |
||
221 | */ |
||
222 | private function isFunction($processor): bool |
||
223 | { |
||
224 | return is_callable($processor) && is_array($processor) === false; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Method searches route processor |
||
229 | * |
||
230 | * @param mixed $processors |
||
231 | * Callable router's processor |
||
232 | * @param string $route |
||
233 | * Route |
||
234 | * @return mixed Result of the router processor |
||
235 | */ |
||
236 | public function findStaticRouteProcessor(&$processors, string $route) |
||
237 | { |
||
238 | foreach ($processors as $i => $processor) { |
||
239 | // exact router or 'all router' |
||
240 | if ($i == $route || $i == '/*/') { |
||
241 | if ($this->isFunction($processor)) { |
||
242 | return $processor($route, []); |
||
243 | } |
||
244 | |||
245 | $functionName = $processor[1] ?? null; |
||
246 | |||
247 | if ($this->canBeCalled($processor, $functionName)) { |
||
248 | // passing route path and parameters |
||
249 | return call_user_func($processor, $route, []); |
||
250 | } else { |
||
251 | $callableDescription = \Mezon\Router\Utils::getCallableDescription($processor); |
||
252 | |||
253 | if ($this->methodDoesNotExists($processor, $functionName)) { |
||
254 | throw (new \Exception("'$callableDescription' does not exists")); |
||
255 | } else { |
||
256 | throw (new \Exception("'$callableDescription' must be callable entity")); |
||
257 | } |
||
258 | } |
||
259 | } |
||
260 | } |
||
261 | |||
262 | return false; |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Method returns route parameter |
||
267 | * |
||
268 | * @param string $name |
||
269 | * Route parameter |
||
270 | * @return string Route parameter |
||
271 | */ |
||
272 | public function getParam(string $name): string |
||
273 | { |
||
274 | if (isset($this->parameters[$name]) === false) { |
||
275 | throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1)); |
||
276 | } |
||
277 | |||
278 | return $this->parameters[$name]; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Does parameter exists |
||
283 | * |
||
284 | * @param string $name |
||
285 | * Param name |
||
286 | * @return bool True if the parameter exists |
||
287 | */ |
||
288 | public function hasParam(string $name): bool |
||
289 | { |
||
290 | return isset($this->parameters[$name]); |
||
291 | } |
||
292 | } |