-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMapRoute.php
370 lines (327 loc) · 10.3 KB
/
MapRoute.php
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
<?php declare(strict_types=1);
namespace PhpSlides\Router;
use PhpSlides\Core\Controller\Controller;
use PhpSlides\Core\Foundation\Application;
use PhpSlides\Router\Interface\MapInterface;
/**
* Class MapRoute
*
* This class is responsible for mapping and matching routes against the current request URI and HTTP method.
* It extends the Controller class and implements the MapInterface interface.
*
* @author dconco <info@dconco.dev>
* @version 1.4.0
* @package PhpSlides
*/
class MapRoute extends Controller implements MapInterface
{
use \PhpSlides\Core\Utils\Validate;
use \PhpSlides\Core\Utils\Routes\StrictTypes;
/**
* @var string|array $route The route(s) to be mapped.
*/
private static string|array $route;
/**
* @var string $request_uri The URI of the current request.
*/
private static string $request_uri;
/**
* @var array $method An array to store HTTP methods for routing.
*/
private static array $method;
/**
* Matches the given HTTP method and route against the current request URI.
*
* @param string $method The HTTP method(s) to match, separated by '|'.
* @param string|array $route The route pattern(s) to match.
* @return bool|array Returns false if no match is found, or an array with the matched method, route, and parameters if a match is found.
*
* The function performs the following steps:
* - Sets the HTTP method(s) to match.
* - Normalizes the request URI by removing leading and trailing slashes and converting to lowercase.
* - Normalizes the route pattern(s) by removing leading and trailing slashes and converting to lowercase.
* - Checks if the route contains a pattern and resolves it if necessary.
* - Extracts parameter names from the route pattern.
* - Matches the request URI against the route pattern and extracts parameter values.
* - Constructs a regex pattern to match the route.
* - Checks if the request method is allowed for the matched route.
* - Returns an array with the matched method, route, and parameters if a match is found.
* - Returns false if no match is found.
*/
public function match(string $method, string|array $route): bool|array
{
self::$method = explode('|', $method);
/**
* ----------------------------------------------
* | Replacing first and last forward slashes
* | $_REQUEST['uri'] will be empty if req uri is /
* ----------------------------------------------
*/
self::$request_uri =
preg_replace("/(^\/)|(\/$)/", '', Application::$request_uri);
self::$request_uri = empty(self::$request_uri) ? '/' : self::$request_uri;
self::$route = is_array($route)
? $route
: preg_replace("/(^\/)|(\/$)/", '', $route);
// Firstly, resolve route with pattern
if (is_array(self::$route))
{
foreach (self::$route as $value)
{
if (str_starts_with($value, 'pattern:'))
{
if ($p = $this->pattern())
{
return $p;
}
}
}
}
elseif (str_starts_with(self::$route, 'pattern:'))
{
return $this->pattern();
}
// will store all the parameters value in this array
$req = [];
$unvalidate_req = [];
$req_value = [];
// will store all the parameters names in this array
$paramKey = [];
// finding if there is any {?} parameter in $route
if (is_string(self::$route))
{
preg_match_all('/(?<={).+?(?=})/', self::$route, $paramMatches);
}
// if the route does not contain any param call routing();
if (
empty($paramMatches) ||
empty($paramMatches[0] ?? []) ||
is_array(self::$route)
)
{
/**
* ------------------------------------------------------
* | Check if $callback is a callable function
* | or array of controller, and if not,
* | it's a string of text or html document
* ------------------------------------------------------
*/
return $this->match_routing();
}
// setting parameters names
foreach ($paramMatches[0] as $key)
{
$paramKey[] = $key;
}
// exploding route address
$uri = explode('/', self::$route);
// will store index number where {?} parameter is required in the $route
$indexNum = [];
// storing index number, where {?} parameter is required with the help of regex
foreach ($uri as $index => $param)
{
if (preg_match('/{.*}/', $param))
{
$indexNum[] = $index;
}
}
/**
* ----------------------------------------------------------------------------------
* | Exploding request uri string to array to get the exact index number value of parameter from $_REQUEST['uri']
* ----------------------------------------------------------------------------------
*/
$reqUri = explode('/', self::$request_uri);
/**
* ----------------------------------------------------------------------------------
* | Running for each loop to set the exact index number with reg expression this will help in matching route
* ----------------------------------------------------------------------------------
*/
foreach ($indexNum as $key => $index)
{
/**
* --------------------------------------------------------------------------------
* | In case if req uri with param index is empty then return because URL is not valid for this route
* --------------------------------------------------------------------------------
*/
if (empty($reqUri[$index]))
{
return false;
}
if (str_contains($paramKey[$key], ':'))
{
$unvalidate_req[] = [ $paramKey[$key], $reqUri[$index] ];
}
// setting params with params names
$key = trim((string) explode(':', $paramKey[$key], 2)[0]);
$req[$key] = $reqUri[$index];
$req_value[] = $reqUri[$index];
// this is to create a regex for comparing route address
$reqUri[$index] = '{.*}';
}
// converting array to string
$reqUri = implode('/', $reqUri);
/**
* -----------------------------------
* | replace all / with \/ for reg expression
* | regex to match route is ready!
* -----------------------------------
*/
$reqUri = str_replace('/', '\\/', $reqUri);
$route = self::$route;
if (Application::$caseInSensitive === true) {
$reqUri = strtolower($reqUri);
$route = strtolower($route);
}
// now matching route with regex
if (preg_match("/$reqUri/", $route . '$'))
{
// checks if the requested method is of the given route
if (
!in_array($_SERVER['REQUEST_METHOD'], self::$method) &&
!in_array('*', self::$method)
)
{
http_response_code(405);
exit('Method Not Allowed');
}
if (!empty($unvalidate_req))
{
foreach ($unvalidate_req as $value)
{
$param_name = trim((string) explode(':', $value[0], 2)[0]);
$param_types = trim((string) explode(':', $value[0], 2)[1]);
$param_types = preg_split('/\|(?![^<]*>)/', $param_types);
$param_value = $value[1];
$parsed_value = static::matchStrictType(
$param_value,
$param_types,
);
$req[$param_name] = $parsed_value;
}
}
return [
'method' => $_SERVER['REQUEST_METHOD'],
'route' => self::$route,
'params_value' => $req_value,
'params' => $req,
];
}
return false;
}
/**
* Matches the current request URI and method against the defined routes.
*
* This method checks if the current request URI matches any of the defined routes
* and if the request method is allowed for the matched route. If a match is found,
* it returns an array containing the request method and the matched route. If no
* match is found, it returns false.
*
* @return bool|array Returns an array with 'method' and 'route' keys if a match is found, otherwise false.
*/
private function match_routing (): bool|array
{
$uri = [];
$str_route = '';
$request_uri = self::$request_uri;
if (is_array(self::$route))
{
for ($i = 0; $i < count(self::$route); $i++)
{
$each_route = preg_replace("/(^\/)|(\/$)/", '', self::$route[$i]);
empty($each_route)
? array_push($uri, '/')
: array_push($uri, Application::$caseInSensitive === true ? strtolower($each_route) : $each_route);
}
}
else
{
$str_route = empty(self::$route) ? '/' : self::$route;
}
if (Application::$caseInSensitive === true) {
$request_uri = strtolower($request_uri);
$str_route = strtolower($str_route);
}
if (
in_array(self::$request_uri, $uri) ||
self::$request_uri === $str_route
)
{
if (
!in_array($_SERVER['REQUEST_METHOD'], self::$method) &&
!in_array('*', self::$method)
)
{
http_response_code(405);
exit('Method Not Allowed');
}
return [
'method' => $_SERVER['REQUEST_METHOD'],
'route' => self::$route,
];
}
else
{
return false;
}
}
/**
* Validates and matches a route pattern.
*
* This method checks if the route is an array and iterates through each value to find a pattern match.
* If a pattern match is found, it validates the pattern and returns the matched result.
* If no match is found in the array, it returns false.
* If the route is not an array, it directly validates the pattern and returns the result.
*
* @return array|bool The matched pattern as an array if found, or false if no match is found.
*/
private function pattern (): array|bool
{
if (is_array(self::$route))
{
foreach (self::$route as $value)
{
if (str_starts_with('pattern:', $value))
{
$matched = $this->validatePattern($value);
if ($matched)
{
return $matched;
}
}
}
return false;
}
return $this->validatePattern(self::$route);
}
/**
* Validates the given pattern against the request URI and checks the request method.
*
* @param string $pattern The pattern to validate.
* @return array|bool Returns an array with the request method and route if the pattern matches, otherwise false.
*/
private function validatePattern (string $pattern): array|bool
{
$request_uri = self::$request_uri;
$pattern = preg_replace("/(^\/)|(\/$)/", '', trim(substr($pattern, 8)));
if (Application::$caseInSensitive === true) {
$request_uri = strtolower($request_uri);
$pattern = strtolower($pattern);
}
if (fnmatch($pattern, $request_uri))
{
if (
!in_array($_SERVER['REQUEST_METHOD'], self::$method) &&
!in_array('*', self::$method)
)
{
http_response_code(405);
exit('Method Not Allowed');
}
return [
'method' => $_SERVER['REQUEST_METHOD'],
'route' => self::$route,
];
}
return false;
}
}