Router::createDispatcher()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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