Completed
Push — master ( 1a7521...b79352 )
by Charis
03:14
created

Router::handleException()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 3
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;
0 ignored issues
show
Unused Code introduced by
return null; does not seem to be 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 return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

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.

Loading history...
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