1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Resilient; |
4
|
|
|
|
5
|
|
|
use BadMethodCallException; |
6
|
|
|
use \Resilient\Route; |
7
|
|
|
use \Resilient\Design\RouteableInterface; |
8
|
|
|
use \Resilient\Traits\Routeable; |
9
|
|
|
use \Resilient\Traits\Bindable; |
10
|
|
|
use \FastRoute\Dispatcher; |
11
|
|
|
use \FastRoute\RouteCollector; |
12
|
|
|
use \FastRoute\RouteParser; |
13
|
|
|
use \FastRoute\RouteParser\Std as StdParser; |
14
|
|
|
use \Psr\Http\Message\UriInterface; |
15
|
|
|
use \Psr\SimpleCache\CacheInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Router class. |
19
|
|
|
* |
20
|
|
|
* @implements RouteableInterface |
21
|
|
|
* @method callable run(array $args) |
22
|
|
|
*/ |
23
|
|
|
class Router implements RouteableInterface |
24
|
|
|
{ |
25
|
|
|
use Routeable, Bindable; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* notFoundFuncName |
29
|
|
|
* |
30
|
|
|
* (default value: 'notFoundHandler') |
31
|
|
|
* |
32
|
|
|
* @var string |
33
|
|
|
* @access protected |
34
|
|
|
*/ |
35
|
|
|
protected $notFoundFuncName = 'notFoundHandler'; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* forbiddenFuncName |
39
|
|
|
* |
40
|
|
|
* (default value: 'forbidenMethodHandler') |
41
|
|
|
* |
42
|
|
|
* @var string |
43
|
|
|
* @access protected |
44
|
|
|
*/ |
45
|
|
|
protected $forbiddenFuncName = 'forbidenMethodHandler'; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* dispatch_result |
49
|
|
|
* |
50
|
|
|
* @var mixed |
51
|
|
|
* @access protected |
52
|
|
|
*/ |
53
|
|
|
protected $dispatch_result; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* routes |
57
|
|
|
* |
58
|
|
|
* (default value: []) |
59
|
|
|
* |
60
|
|
|
* @var mixed |
61
|
|
|
* @access protected |
62
|
|
|
*/ |
63
|
|
|
protected $routes = []; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* routeCount |
67
|
|
|
* |
68
|
|
|
* (default value: 0) |
69
|
|
|
* |
70
|
|
|
* @var int |
71
|
|
|
* @access protected |
72
|
|
|
*/ |
73
|
|
|
protected $routeCount = 0; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* routeGroup |
77
|
|
|
* |
78
|
|
|
* @var mixed |
79
|
|
|
* @access protected |
80
|
|
|
*/ |
81
|
|
|
protected $routeGroup; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* parser |
85
|
|
|
* |
86
|
|
|
* @var mixed |
87
|
|
|
* @access protected |
88
|
|
|
*/ |
89
|
|
|
protected $parser; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* dispatcher |
93
|
|
|
* |
94
|
|
|
* @var mixed |
95
|
|
|
* @access protected |
96
|
|
|
*/ |
97
|
|
|
protected $dispatcher; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* cacheEngine |
101
|
|
|
* |
102
|
|
|
* @var mixed |
103
|
|
|
* @access protected |
104
|
|
|
*/ |
105
|
|
|
protected $cacheEngine; |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* cacheKey |
109
|
|
|
* |
110
|
|
|
* (default value: "{Resilient\Router}/router.cache") |
111
|
|
|
* |
112
|
|
|
* @var string |
113
|
|
|
* @access protected |
114
|
|
|
*/ |
115
|
|
|
protected $cacheKey = "{Resilient\Router}/router.cache"; |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* cacheTtl |
119
|
|
|
* |
120
|
|
|
* (default value: 86400) |
121
|
|
|
* |
122
|
|
|
* @var int |
123
|
|
|
* @access protected |
124
|
|
|
*/ |
125
|
|
|
protected $cacheTtl = 86400; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* apiHandler |
129
|
|
|
* |
130
|
|
|
* @var mixed |
131
|
|
|
* @access protected |
132
|
|
|
*/ |
133
|
|
|
protected $apiHandler; |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* notFoundHandler |
137
|
|
|
* |
138
|
|
|
* @var mixed |
139
|
|
|
* @access protected |
140
|
|
|
*/ |
141
|
|
|
protected $notFoundHandler; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* methodNotAllowedHandler |
145
|
|
|
* |
146
|
|
|
* @var mixed |
147
|
|
|
* @access protected |
148
|
|
|
*/ |
149
|
|
|
protected $methodNotAllowedHandler; |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* __construct function. |
153
|
|
|
* |
154
|
|
|
* @access public |
155
|
|
|
* @param mixed $parser |
156
|
|
|
*/ |
157
|
|
|
public function __construct($parser) |
158
|
|
|
{ |
159
|
|
|
$this->parser = $parser ?: new StdParser; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* setDispatcher function. |
164
|
|
|
* |
165
|
|
|
* @access public |
166
|
|
|
* @param Dispatcher $dispatcher |
167
|
|
|
* @return Router |
168
|
|
|
*/ |
169
|
|
|
public function setDispatcher(Dispatcher $dispatcher) |
170
|
|
|
{ |
171
|
|
|
$this->dispatcher = $dispatcher; |
172
|
|
|
|
173
|
|
|
return $this; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* setCacheEngine function. |
178
|
|
|
* |
179
|
|
|
* @access public |
180
|
|
|
* @param CacheInterface $cacheEngine |
181
|
|
|
* @return Router |
182
|
|
|
*/ |
183
|
|
|
public function setCacheEngine(CacheInterface $cacheEngine) |
184
|
|
|
{ |
185
|
|
|
$this->cacheEngine = $cacheEngine; |
186
|
|
|
|
187
|
|
|
return $this; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* setCacheTtl function. |
192
|
|
|
* |
193
|
|
|
* @access public |
194
|
|
|
* @param int $cacheTtl |
195
|
|
|
* @return Router |
196
|
|
|
*/ |
197
|
|
|
public function setCacheTtl(int $cacheTtl) |
198
|
|
|
{ |
199
|
|
|
$this->cacheTtl = $cacheTtl; |
200
|
|
|
|
201
|
|
|
return $this; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* setCacheKey function. |
206
|
|
|
* |
207
|
|
|
* @access public |
208
|
|
|
* @param string $cacheKey |
209
|
|
|
* @return Router |
210
|
|
|
*/ |
211
|
|
|
public function setCacheKey(string $cacheKey) |
212
|
|
|
{ |
213
|
|
|
$this->cacheKey = $cacheKey; |
214
|
|
|
return $this; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* getRoute function. |
219
|
|
|
* |
220
|
|
|
* @access public |
221
|
|
|
* @param string $identifier |
222
|
|
|
* @return null|Route |
223
|
|
|
*/ |
224
|
|
|
public function getRoute(string $identifier) |
225
|
|
|
{ |
226
|
|
|
return !empty($this->routes[$identifier]) ? $this->routes[$identifier] : null; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* getRoutes function. |
231
|
|
|
* |
232
|
|
|
* @access public |
233
|
|
|
* @return array |
234
|
|
|
*/ |
235
|
|
|
public function getRoutes() |
236
|
|
|
{ |
237
|
|
|
return $this->routes; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* setRoutes function. |
242
|
|
|
* |
243
|
|
|
* @access public |
244
|
|
|
* @param array $method |
245
|
|
|
* @param array $routes |
246
|
|
|
* @return Router |
247
|
|
|
*/ |
248
|
|
|
public function setRoutes(array $method, array $routes) |
249
|
|
|
{ |
250
|
|
|
foreach ($routes as $pattern => $handler) { |
251
|
|
|
$this->map($method, $pattern, $handler); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
return $this; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* getResult function. |
259
|
|
|
* |
260
|
|
|
* @access public |
261
|
|
|
* @return void |
262
|
|
|
*/ |
263
|
|
|
public function getResult() |
264
|
|
|
{ |
265
|
|
|
return $this->dispatch_result; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* {@inheritdoc} |
270
|
|
|
*/ |
271
|
|
|
public function map($method, string $pattern, $handler) |
272
|
|
|
{ |
273
|
|
|
$method = is_array($method) ? $method : [$method]; |
274
|
|
|
|
275
|
|
|
foreach ($method as $m) { |
276
|
|
|
$route = $this->createRoute($m, $pattern, $handler); |
277
|
|
|
|
278
|
|
|
$this->routeCount++; |
279
|
|
|
|
280
|
|
|
$this->routes[$route->getIdentifier()] = $route; |
281
|
|
|
|
282
|
|
|
if (is_callable($handler)) { |
283
|
|
|
$route->bind('run', $handler); |
284
|
|
|
} |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
return $this; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* createRoute function. |
292
|
|
|
* |
293
|
|
|
* @access protected |
294
|
|
|
* @param string $method |
295
|
|
|
* @param string $pattern |
296
|
|
|
* @param mixed $handler |
297
|
|
|
* @return Route Route |
298
|
|
|
*/ |
299
|
|
|
protected function createRoute(string $method, string $pattern, $handler) |
300
|
|
|
{ |
301
|
|
|
return new Route($method, $pattern, $handler, $this->routeGroup, 'route_' . $this->routeCount); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* routeDispatcher function. |
306
|
|
|
* |
307
|
|
|
* @access protected |
308
|
|
|
* @param callable $routeDefinitionCallback |
309
|
|
|
* @param array $options (default: []) |
310
|
|
|
* @return Dispatcher |
311
|
|
|
*/ |
312
|
|
|
protected function routeDispatcher(callable $routeDefinitionCallback, array $options = []) |
313
|
|
|
{ |
314
|
|
|
$options += [ |
315
|
|
|
'routeParser' => 'FastRoute\\RouteParser\\Std', |
316
|
|
|
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', |
317
|
|
|
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', |
318
|
|
|
'routeCollector' => 'FastRoute\\RouteCollector' |
319
|
|
|
]; |
320
|
|
|
|
321
|
|
|
$dispatchDataRunner = function () use ($routeDefinitionCallback, $options) { |
322
|
|
|
$routeCollector = new $options['routeCollector']( |
323
|
|
|
new $options['routeParser'], new $options['dataGenerator'] |
324
|
|
|
); |
325
|
|
|
$routeDefinitionCallback($routeCollector); |
326
|
|
|
|
327
|
|
|
return $routeCollector->getData(); |
328
|
|
|
}; |
329
|
|
|
|
330
|
|
|
if (!empty($this->cacheEngine) && !empty($this->cacheKey)) { |
331
|
|
|
if ($this->cacheEngine->has($this->cacheKey)) { |
332
|
|
|
$dispatchData = $this->cacheEngine->get($this->cacheKey); |
333
|
|
|
|
334
|
|
|
return new $options['dispatcher']($dispatchData); |
335
|
|
|
} else { |
336
|
|
|
$dispatchData = $dispatchDataRunner(); |
337
|
|
|
$this->cacheEngine->set($this->cacheKey, $dispatchData, $this->cacheTtl); |
338
|
|
|
|
339
|
|
|
return $dispatchData; |
340
|
|
|
} |
341
|
|
|
} else { |
342
|
|
|
return new $options['dispatcher']($dispatchDataRunner()); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* createDispatcher function. |
349
|
|
|
* |
350
|
|
|
* @access protected |
351
|
|
|
* @return Dispatcher |
352
|
|
|
*/ |
353
|
|
|
protected function createDispatcher() |
354
|
|
|
{ |
355
|
|
|
if ($this->dispatcher) { |
356
|
|
|
return $this->dispatcher; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
$routeDefinitionCallback = function (RouteCollector $r) { |
360
|
|
|
foreach ($this->getRoutes() as $route) { |
361
|
|
|
$r->addRoute($route->getMethod(), $route->getPattern(), $route->getIdentifier()); |
362
|
|
|
} |
363
|
|
|
}; |
364
|
|
|
|
365
|
|
|
$this->dispatcher = $this->routeDispatcher($routeDefinitionCallback, [ |
366
|
|
|
'routeParser' => $this->parser, |
367
|
|
|
]); |
368
|
|
|
|
369
|
|
|
return $this->dispatcher; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* dispatch function. |
374
|
|
|
* |
375
|
|
|
* @access public |
376
|
|
|
* @param UriInterface $uri |
377
|
|
|
* @param string $method (default: 'GET') |
378
|
|
|
* @return Route Handling Method |
379
|
|
|
*/ |
380
|
|
|
public function dispatch(UriInterface $uri, $method = 'GET') |
381
|
|
|
{ |
382
|
|
|
$this->dispatch_result = $this->createDispatcher()->dispatch( |
383
|
|
|
$method, |
384
|
|
|
$uri->getPath() |
385
|
|
|
); |
386
|
|
|
|
387
|
|
|
$functionHandler = function ($arg) use ($uri, $method) { |
388
|
|
|
if (method_exists($this, $arg['methodName']) || $this->hasMethod($arg['methodName'])) { |
389
|
|
|
return $this->{$arg['methodName']}(...$arg['args']); |
390
|
|
|
} else { |
391
|
|
|
return $this->handleException($arg['methodName'], $uri, $method); |
392
|
|
|
} |
393
|
|
|
}; |
394
|
|
|
|
395
|
|
|
$code = array_shift($this->dispatch_result); |
396
|
|
|
|
397
|
|
|
$handlerMapper = [ |
398
|
|
|
Dispatcher::NOT_FOUND => [ |
399
|
|
|
'methodName' => $this->notFoundFuncName, |
400
|
|
|
'args' => [$uri, $method] |
401
|
|
|
], |
402
|
|
|
Dispatcher::METHOD_NOT_ALLOWED => [ |
403
|
|
|
'methodName' => $this->forbiddenFuncName, |
404
|
|
|
'args' => [$uri, $method] |
405
|
|
|
], |
406
|
|
|
Dispatcher::FOUND => [ |
407
|
|
|
'methodName' => 'routerRoutine', |
408
|
|
|
'args' => $this->dispatch_result |
409
|
|
|
] |
410
|
|
|
]; |
411
|
|
|
|
412
|
|
|
return $functionHandler($handlerMapper[$code]); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
protected function handleException($exception, $uri, $method) |
416
|
|
|
{ |
417
|
|
|
if ($exception === $this->notFoundFuncName) { |
418
|
|
|
throw new BadMethodCallException('Method : ' . ((string) $method) . ' ON uri : ' . ((string) $uri) . ' Not Allowed'); |
419
|
|
|
} elseif ($exception === $this->forbiddenFuncName) { |
420
|
|
|
throw new BadMethodCallException(((string) $uri) . ' Not Available'); |
421
|
|
|
} else { |
422
|
|
|
throw new BadMethodCallException('There is no method or exception to handle this request ' . ((string) $uri)); |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
return null; |
|
|
|
|
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* whenNotFound function. |
430
|
|
|
* |
431
|
|
|
* @access public |
432
|
|
|
* @param callable $callable |
433
|
|
|
* @return Router |
434
|
|
|
*/ |
435
|
|
|
public function whenNotFound(callable $callable) |
436
|
|
|
{ |
437
|
|
|
$this->bind($this->notFoundFuncName, $callable); |
438
|
|
|
|
439
|
|
|
return $this; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* whenForbidden function. |
444
|
|
|
* |
445
|
|
|
* @access public |
446
|
|
|
* @param callable $callable |
447
|
|
|
* @return Router |
448
|
|
|
*/ |
449
|
|
|
public function whenForbidden(callable $callable) |
450
|
|
|
{ |
451
|
|
|
$this->bind($this->forbiddenFuncName, $callable); |
452
|
|
|
|
453
|
|
|
return $this; |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
/** |
457
|
|
|
* routerRoutine function. |
458
|
|
|
* |
459
|
|
|
* @access protected |
460
|
|
|
* @param mixed $identifier |
461
|
|
|
* @param mixed $args |
462
|
|
|
* @return void |
463
|
|
|
*/ |
464
|
|
|
protected function routerRoutine($identifier, $args) |
465
|
|
|
{ |
466
|
|
|
$route = $this->getRoute($identifier); |
467
|
|
|
|
468
|
|
|
if (!empty($args)) { |
469
|
|
|
foreach ($args as &$v) { |
470
|
|
|
$v = urldecode($v); |
471
|
|
|
} |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
if ($route->hasMethod('run')) { |
475
|
|
|
return $route->run($args); |
476
|
|
|
} else { |
477
|
|
|
return $route->setArgs($args); |
478
|
|
|
} |
479
|
|
|
} |
480
|
|
|
} |
481
|
|
|
|
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
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.