Test Failed
Push — master ( fc0593...67b45a )
by Charis
03:25
created

Router::saveCacheItem()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

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 6
nc 3
nop 1
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\CacheItemPoolInterface;
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
     * cachePool
101
     *
102
     * @var mixed
103
     * @access protected
104
     */
105
    protected $cachePool;
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
     * setcachePool function.
178
     *
179
     * @access public
180
     * @param CacheInterface $cachePool
181
     * @return Router
182
     */
183
    public function setCachePool(CacheItemPoolInterface $cachePool)
184
    {
185
        $this->cachePool = $cachePool;
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
     * cacheAble function.
306
     *
307
     * @access protected
308
     * @return void
309
     */
310
    protected function cacheAble()
311
    {
312
        return !empty($this->cachePool) && !empty($this->cacheKey);
313
    }
314
315
    /**
316
     * getCacheItem function.
317
     *
318
     * @access protected
319
     * @return CacheItemInterface
320
     */
321
    protected function getCacheItem()
322
    {
323
        return $this->cacheAble() ? $this->cachePool->getItem($this->cacheKey) : new \Resilient\Dummy\CacheItem();
324
    }
325
326
    protected function saveCacheItem(CacheItemInterface $cacheItem)
327
    {
328
        if ($cacheItem instanceof \Resilient\Dummy\CacheItem) {
329
            return false;
330
        }
331
332
        if (!empty($this->cacheTtl)) {
333
            $cacheItem->expiresAfter($this->cacheTtl);
334
        }
335
336
        return $this->cachePool->save($cacheItem);
337
    }
338
339
    /**
340
     * routeDispatcher function.
341
     *
342
     * @access protected
343
     * @param callable $routeDefinitionCallback
344
     * @param array $options (default: [])
345
     * @return Dispatcher
346
     */
347
    protected function routeDispatcher(callable $routeDefinitionCallback, array $options = [])
348
    {
349
        $options += [
350
            'routeParser' => 'FastRoute\\RouteParser\\Std',
351
            'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
352
            'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
353
            'routeCollector' => 'FastRoute\\RouteCollector'
354
        ];
355
356
        $cacheItem = $this->getCacheItem();
357
358
        if ($cacheItem->isHit()) {
359
            return new $options['dispatcher']($cacheItem->get($this->cacheKey));
360
        }
361
362
        $routeCollector = new $options['routeCollector'](
363
            new $options['routeParser'], new $options['dataGenerator']
364
        );
365
        $routeDefinitionCallback($routeCollector);
366
367
        $dispatchData = $routeCollector->getData();
368
369
        $cacheItem->set($dispatchData);
370
371
        $this->saveCacheItem($cacheItem);
372
373
        return new $options['dispatcher']($dispatchData);
374
    }
375
376
    /**
377
     * createDispatcher function.
378
     *
379
     * @access protected
380
     * @return Dispatcher
381
     */
382
    protected function createDispatcher()
383
    {
384
        if ($this->dispatcher) {
385
            return $this->dispatcher;
386
        }
387
388
        $this->dispatcher = $this->routeDispatcher(function (RouteCollector $r) {
389
            foreach ($this->getRoutes() as $route) {
390
                $r->addRoute($route->getMethod(), $route->getPattern(), $route->getIdentifier());
391
            }
392
        }, [
393
            'routeParser' => $this->parser,
394
        ]);
395
396
        return $this->dispatcher;
397
    }
398
399
    /**
400
     * dispatch function.
401
     *
402
     * @access public
403
     * @param UriInterface $uri
404
     * @param string $method (default: 'GET')
405
     * @return Route Handling Method
406
     */
407
    public function dispatch(UriInterface $uri, $method = 'GET')
408
    {
409
        $this->dispatch_result = $this->createDispatcher()->dispatch(
410
            $method,
411
            $uri->getPath()
412
        );
413
414
        $code = array_shift($this->dispatch_result);
415
416
        if ($code == Dispatcher::FOUND) {
417
            return $this->routerRoutine($this->dispatch_result[0], $this->dispatch_result[1]);
418
        }
419
420
        $exceptionMapper = [
421
            Dispatcher::NOT_FOUND => [
422
                $this->notFoundFuncName,
423
                [$uri, $method],
424
                ((string) $uri) . ' Not Available'
425
            ],
426
            Dispatcher::METHOD_NOT_ALLOWED => [
427
                $this->forbiddenFuncName,
428
                [$uri, $method],
429
                'Method : ' . ((string) $method) . ' ON uri : ' . ((string) $uri) . ' Not Allowed'
430
            ]
431
        ];
432
433
        $handling = $exceptionMapper[$code];
434
435
        return $this->handleException($handling[0], $handling[1], $handling[2]);
436
    }
437
438
    protected function handleException(string $handler, array $args, string $message)
439
    {
440
        if ($this->hasMethod($handler)) {
441
            return $this->$handler(...$args);
442
        }
443
444
        throw new BadMethodCallException($message);
445
    }
446
447
    /**
448
     * whenNotFound function.
449
     *
450
     * @access public
451
     * @param callable $callable
452
     * @return Router
453
     */
454
    public function whenNotFound(callable $callable)
455
    {
456
        $this->bind($this->notFoundFuncName, $callable);
457
458
        return $this;
459
    }
460
461
    /**
462
     * whenForbidden function.
463
     *
464
     * @access public
465
     * @param callable $callable
466
     * @return Router
467
     */
468
    public function whenForbidden(callable $callable)
469
    {
470
        $this->bind($this->forbiddenFuncName, $callable);
471
472
        return $this;
473
    }
474
475
    /**
476
     * routerRoutine function.
477
     *
478
     * @access protected
479
     * @param mixed $identifier
480
     * @param mixed $args
481
     * @return void
482
     */
483
    protected function routerRoutine($identifier, $args)
484
    {
485
        $route = $this->getRoute($identifier);
486
487
        if (!empty($args)) {
488
            foreach ($args as &$v) {
489
                $v = urldecode($v);
490
            }
491
        }
492
493
        if ($route->hasMethod('run')) {
494
            return $route->run($args);
495
        }
496
497
        return $route->setArgs($args);
498
    }
499
}
500