Passed
Push — master ( b29a96...ccf9ca )
by Marcio
02:49
created

Router   F

Complexity

Total Complexity 124

Size/Duplication

Total Lines 840
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 124
eloc 313
c 1
b 0
f 1
dl 0
loc 840
rs 2

28 Methods

Rating   Name   Duplication   Size   Complexity  
A loadCache() 0 9 2
A exception() 0 3 1
A middlewareBefore() 0 5 1
A resolveClass() 0 23 3
C group() 0 55 13
F addRoute() 0 39 12
A runRouteCommand() 0 3 1
A name() 0 12 2
A endGroup() 0 3 1
C controller() 0 55 15
C setPaths() 0 35 13
B __call() 0 49 11
A pattern() 0 17 5
A runRouteMiddleware() 0 11 4
A clearRouteName() 0 4 2
A setMiddleware() 0 3 1
A getRoutes() 0 3 1
A getList() 0 6 1
A error() 0 3 1
A add() 0 22 6
A __construct() 0 12 3
C run() 0 71 14
A middleware() 0 12 3
A setMiddlewareGroup() 0 3 1
A cache() 0 14 4
A middlewareAfter() 0 5 1
A routerCommand() 0 3 1
A setRouteMiddleware() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Router often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Router, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @Package: Router - simple router class for php
4
 * @Class  : Router
5
 * @Author : izni burak demirtas / @izniburak <[email protected]>
6
 * @Web    : http://burakdemirtas.org
7
 * @URL    : https://github.com/izniburak/php-router
8
 * @Licence: The MIT License (MIT) - Copyright (c) - http://opensource.org/licenses/MIT
9
 */
10
11
namespace Ballybran\Routing;
12
13
use Ballybran\Routing\Router\RouterCommand;
14
use Ballybran\Routing\Router\RouterException;
15
use Ballybran\Routing\Router\RouterRequest;
16
use Closure;
17
use Exception;
18
use ReflectionMethod;
19
20
/**
21
 * Class Router
22
 *
23
 * @method $this any($route, $settings, $callback = null)
24
 * @method $this get($route, $settings, $callback = null)
25
 * @method $this post($route, $settings, $callback = null)
26
 * @method $this put($route, $settings, $callback = null)
27
 * @method $this delete($route, $settings, $callback = null)
28
 * @method $this patch($route, $settings, $callback = null)
29
 * @method $this head($route, $settings, $callback = null)
30
 * @method $this options($route, $settings, $callback = null)
31
 * @method $this xpost($route, $settings, $callback = null)
32
 * @method $this xput($route, $settings, $callback = null)
33
 * @method $this xdelete($route, $settings, $callback = null)
34
 * @method $this xpatch($route, $settings, $callback = null)
35
 *
36
 * @package Buki
37
 */
38
class Router
39
{
40
    /**
41
     * @var string
42
     */
43
    protected $documentRoot = '';
44
45
    /**
46
     * @var string
47
     */
48
    protected $runningPath = '';
49
50
    /**
51
     * @var string $baseFolder Pattern definitions for parameters of Route
52
     */
53
    protected $baseFolder;
54
55
    /**
56
     * @var array $routes Routes list
57
     */
58
    protected $routes = [];
59
60
    /**
61
     * @var array $groups List of group routes
62
     */
63
    protected $groups = [];
64
65
    /**
66
     * @var array $patterns Pattern definitions for parameters of Route
67
     */
68
    protected $patterns = [
69
        ':id' => '(\d+)',
70
        ':number' => '(\d+)',
71
        ':any' => '([^/]+)',
72
        ':all' => '(.*)',
73
        ':string' => '(\w+)',
74
        ':slug' => '([\w\-_]+)',
75
    ];
76
77
    /**
78
     * @var array $namespaces Namespaces of Controllers and Middlewares files
79
     */
80
    protected $namespaces = [
81
        'middlewares' => '',
82
        'controllers' => '',
83
    ];
84
85
    /**
86
     * @var array $path Paths of Controllers and Middlewares files
87
     */
88
    protected $paths = [
89
        'controllers' => 'Controllers',
90
        'middlewares' => 'Middlewares',
91
    ];
92
93
    /**
94
     * @var string $mainMethod Main method for controller
95
     */
96
    protected $mainMethod = 'main';
97
98
    /**
99
     * @var string $cacheFile Cache file
100
     */
101
    protected $cacheFile = null;
102
103
    /**
104
     * @var bool $cacheLoaded Cache is loaded?
105
     */
106
    protected $cacheLoaded = false;
107
108
    /**
109
     * @var Closure $errorCallback Route error callback function
110
     */
111
    protected $errorCallback;
112
113
    /**
114
     * @var array $middlewares General middlewares for per request
115
     */
116
    protected $middlewares = [];
117
118
    /**
119
     * @var array $routeMiddlewares Route middlewares
120
     */
121
    protected $routeMiddlewares = [];
122
123
    /**
124
     * @var array $middlewareGroups Middleware Groups
125
     */
126
    protected $middlewareGroups = [];
127
128
    /**
129
     * Router constructor method.
130
     *
131
     * @param array $params
132
     *
133
     * @return void
134
     */
135
    public function __construct(array $params = [])
136
    {
137
        $this->documentRoot = realpath($_SERVER['DOCUMENT_ROOT']);
138
        $this->runningPath = realpath(getcwd());
139
        $this->baseFolder = $this->runningPath;
140
141
        if (isset($params['debug']) && is_bool($params['debug'])) {
142
            RouterException::$debug = $params['debug'];
143
        }
144
145
        $this->setPaths($params);
146
        $this->loadCache();
147
    }
148
149
    /**
150
     * [TODO] This method implementation not completed yet.
151
     *
152
     * Set route middleware
153
     *
154
     * @param string|array $middleware
155
     * @param string $type
156
     *
157
     * @return $this
158
     */
159
    public function middleware($middleware, $type = 'before')
160
    {
161
        if (!is_array($middleware) && !is_string($middleware)) {
0 ignored issues
show
introduced by
The condition is_string($middleware) is always true.
Loading history...
162
            return $this;
163
        }
164
165
        $currentRoute = end($this->routes);
166
        $currentRoute[$type] = $middleware;
167
        array_pop($this->routes);
168
        array_push($this->routes, $currentRoute);
169
170
        return $this;
171
    }
172
173
    /**
174
     * [TODO] This method implementation not completed yet.
175
     *
176
     * @param string|array $middleware
177
     *
178
     * @return $this
179
     */
180
    public function middlewareBefore($middleware)
181
    {
182
        $this->middleware($middleware, 'before');
183
184
        return $this;
185
    }
186
187
    /**
188
     * [TODO] This method implementation not completed yet.
189
     *
190
     * @param string|array $middleware
191
     *
192
     * @return $this
193
     */
194
    public function middlewareAfter($middleware)
195
    {
196
        $this->middleware($middleware, 'after');
197
198
        return $this;
199
    }
200
201
    /**
202
     * [TODO] This method implementation not completed yet.
203
     *
204
     * Set route name
205
     *
206
     * @param string $name
207
     *
208
     * @return $this
209
     */
210
    public function name($name)
211
    {
212
        if (!is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always true.
Loading history...
213
            return $this;
214
        }
215
216
        $currentRoute = end($this->routes);
217
        $currentRoute['name'] = $name;
218
        array_pop($this->routes);
219
        array_push($this->routes, $currentRoute);
220
221
        return $this;
222
    }
223
224
    /**
225
     * [TODO] This method implementation not completed yet.
226
     *
227
     * Set general middlewares
228
     *
229
     * @param array $middlewares
230
     *
231
     * @return void
232
     */
233
    public function setMiddleware(array $middlewares)
234
    {
235
        $this->middlewares = $middlewares;
236
    }
237
238
    /**
239
     * [TODO] This method implementation not completed yet.
240
     *
241
     * Set Route middlewares
242
     *
243
     * @param array $middlewares
244
     *
245
     * @return void
246
     */
247
    public function setRouteMiddleware(array $middlewares)
248
    {
249
        $this->routeMiddlewares = $middlewares;
250
    }
251
252
    /**
253
     * [TODO] This method implementation not completed yet.
254
     *
255
     * Set middleware groups
256
     *
257
     * @param array $middlewareGroup
258
     *
259
     * @return void
260
     */
261
    public function setMiddlewareGroup(array $middlewareGroup)
262
    {
263
        $this->middlewareGroups = $middlewareGroup;
264
    }
265
266
    /**
267
     * Add route method;
268
     * Get, Post, Put, Delete, Patch, Any, Ajax...
269
     *
270
     * @param $method
271
     * @param $params
272
     *
273
     * @return mixed
274
     * @throws
275
     */
276
    public function __call($method, $params)
277
    {
278
        if ($this->cacheLoaded) {
279
            return true;
280
        }
281
282
        if (is_null($params)) {
283
            return false;
284
        }
285
286
        if (!in_array(strtoupper($method), explode('|', RouterRequest::$validMethods))) {
287
            return $this->exception($method . ' is not valid.');
288
        }
289
290
        $route = $params[0];
291
        $callback = $params[1];
292
        $settings = null;
293
294
        if (count($params) > 2) {
295
            $settings = $params[1];
296
            $callback = $params[2];
297
        }
298
299
        if (strstr($route, ':')) {
300
            $route1 = $route2 = '';
301
            foreach (explode('/', $route) as $key => $value) {
302
                if ($value != '') {
303
                    if (!strpos($value, '?')) {
304
                        $route1 .= '/' . $value;
305
                    } else {
306
                        if ($route2 == '') {
307
                            $this->addRoute($route1, $method, $callback, $settings);
308
                        }
309
310
                        $route2 = $route1 . '/' . str_replace('?', '', $value);
311
                        $this->addRoute($route2, $method, $callback, $settings);
312
                        $route1 = $route2;
313
                    }
314
                }
315
            }
316
317
            if ($route2 == '') {
318
                $this->addRoute($route1, $method, $callback, $settings);
319
            }
320
        } else {
321
            $this->addRoute($route, $method, $callback, $settings);
322
        }
323
324
        return $this;
325
    }
326
327
    /**
328
     * Add new route method one or more http methods.
329
     *
330
     * @param string               $methods
331
     * @param string               $route
332
     * @param array|string|closure $settings
333
     * @param string|closure       $callback
334
     *
335
     * @return bool
336
     */
337
    public function add($methods, $route, $settings, $callback = null)
338
    {
339
        if ($this->cacheLoaded) {
340
            return true;
341
        }
342
343
        if (is_null($callback)) {
344
            $callback = $settings;
345
            $settings = null;
346
        }
347
348
        if (strstr($methods, '|')) {
349
            foreach (array_unique(explode('|', $methods)) as $method) {
350
                if (!empty($method)) {
351
                    call_user_func_array([$this, strtolower($method)], [$route, $settings, $callback]);
352
                }
353
            }
354
        } else {
355
            call_user_func_array([$this, strtolower($methods)], [$route, $settings, $callback]);
356
        }
357
358
        return true;
359
    }
360
361
    /**
362
     * Add new route rules pattern; String or Array
363
     *
364
     * @param string|array $pattern
365
     * @param null|string  $attr
366
     *
367
     * @return mixed
368
     * @throws
369
     */
370
    public function pattern($pattern, $attr = null)
371
    {
372
        if (is_array($pattern)) {
373
            foreach ($pattern as $key => $value) {
374
                if (in_array($key, array_keys($this->patterns))) {
375
                    return $this->exception($key . ' pattern cannot be changed.');
376
                }
377
                $this->patterns[$key] = '(' . $value . ')';
378
            }
379
        } else {
380
            if (in_array($pattern, array_keys($this->patterns))) {
381
                return $this->exception($pattern . ' pattern cannot be changed.');
382
            }
383
            $this->patterns[$pattern] = '(' . $attr . ')';
384
        }
385
386
        return true;
387
    }
388
389
    /**
390
     * Run Routes
391
     *
392
     * @return void
393
     * @throws
394
     */
395
    public function run()
396
    {
397
        $base = str_replace('\\', '/', str_replace($this->documentRoot, '', $this->runningPath));
398
        $uri = rtrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
399
        if ($_SERVER['REQUEST_URI'] !== $_SERVER['PHP_SELF']) {
400
            $uri = str_replace(dirname($_SERVER['PHP_SELF']), '', $uri);
401
        }
402
403
        if (($base !== $uri) && (substr($uri, -1) === '/')) {
404
            $uri = substr($uri, 0, (strlen($uri) - 1));
405
        }
406
407
        $uri = $this->clearRouteName($uri);
408
        $method = RouterRequest::getRequestMethod();
409
        $searches = array_keys($this->patterns);
410
        $replaces = array_values($this->patterns);
411
        $foundRoute = false;
412
413
        $routes = array_column($this->routes, 'route');
414
415
        // check if route is defined without regex
416
        if (in_array($uri, $routes)) {
417
            $currentRoute = array_filter($this->routes, function ($r) use ($method, $uri) {
418
                return RouterRequest::validMethod($r['method'], $method) && $r['route'] === $uri;
419
            });
420
            if (!empty($currentRoute)) {
421
                $currentRoute = current($currentRoute);
422
                $foundRoute = true;
423
                $this->runRouteMiddleware($currentRoute, 'before');
424
                $this->runRouteCommand($currentRoute['callback']);
425
                $this->runRouteMiddleware($currentRoute, 'after');
426
            }
427
        } else {
428
            foreach ($this->routes as $data) {
429
                $route = $data['route'];
430
                if (strstr($route, ':') !== false) {
431
                    $route = str_replace($searches, $replaces, $route);
432
                }
433
434
                if (preg_match('#^' . $route . '$#', $uri, $matched)) {
435
                    if (RouterRequest::validMethod($data['method'], $method)) {
436
                        $foundRoute = true;
437
438
                        $this->runRouteMiddleware($data, 'before');
439
440
                        array_shift($matched);
441
                        $matched = array_map(function ($value) {
442
                            return trim(urldecode($value));
443
                        }, $matched);
444
445
                        $this->runRouteCommand($data['callback'], $matched);
446
                        $this->runRouteMiddleware($data, 'after');
447
                        break;
448
                    }
449
                }
450
            }
451
        }
452
453
        // If it originally was a HEAD request, clean up after ourselves by emptying the output buffer
454
        if (strtoupper($_SERVER['REQUEST_METHOD']) === 'HEAD') {
455
            ob_end_clean();
456
        }
457
458
        if ($foundRoute === false) {
0 ignored issues
show
introduced by
The condition $foundRoute === false is always true.
Loading history...
459
            if (!$this->errorCallback) {
460
                $this->errorCallback = function () {
461
                    header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
462
                    return $this->exception('Route not found. Looks like something went wrong. Please try again.');
463
                };
464
            }
465
            call_user_func($this->errorCallback);
466
        }
467
    }
468
469
    /**
470
     * Routes Group
471
     *
472
     * @param string        $name
473
     * @param closure|array $settings
474
     * @param null|closure  $callback
475
     *
476
     * @return bool
477
     */
478
    public function group($name, $settings = null, $callback = null)
479
    {
480
        if ($this->cacheLoaded) {
481
            return true;
482
        }
483
484
        $group = [];
485
        $group['route'] = $this->clearRouteName($name);
486
        $group['before'] = $group['after'] = null;
487
488
        if (is_null($callback)) {
489
            $callback = $settings;
490
        } else {
491
            $group['before'][] = !isset($settings['before']) ? null : $settings['before'];
492
            $group['after'][] = !isset($settings['after']) ? null : $settings['after'];
493
        }
494
495
        $groupCount = count($this->groups);
496
        if ($groupCount > 0) {
497
            $list = [];
498
            foreach ($this->groups as $key => $value) {
499
                if (is_array($value['before'])) {
500
                    foreach ($value['before'] as $k => $v) {
501
                        $list['before'][] = $v;
502
                    }
503
                    foreach ($value['after'] as $k => $v) {
504
                        $list['after'][] = $v;
505
                    }
506
                }
507
            }
508
509
            if (!is_null($group['before'])) {
510
                $list['before'][] = $group['before'][0];
511
            }
512
513
            if (!is_null($group['after'])) {
514
                $list['after'][] = $group['after'][0];
515
            }
516
517
            $group['before'] = $list['before'];
518
            $group['after'] = $list['after'];
519
        }
520
521
        $group['before'] = array_values(array_unique((array)$group['before']));
522
        $group['after'] = array_values(array_unique((array)$group['after']));
523
524
        array_push($this->groups, $group);
525
526
        if (is_object($callback)) {
527
            call_user_func_array($callback, [$this]);
528
        }
529
530
        $this->endGroup();
531
532
        return true;
533
    }
534
535
    /**
536
     * Added route from methods of Controller file.
537
     *
538
     * @param string       $route
539
     * @param string|array $settings
540
     * @param null|string  $controller
541
     *
542
     * @return mixed
543
     * @throws
544
     */
545
    public function controller($route, $settings, $controller = null)
546
    {
547
        if ($this->cacheLoaded) {
548
            return true;
549
        }
550
551
        if (is_null($controller)) {
552
            $controller = $settings;
553
            $settings = [];
554
        }
555
556
        $controller = $this->resolveClass($controller);
557
        $classMethods = get_class_methods($controller);
558
        if ($classMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classMethods of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
559
            foreach ($classMethods as $methodName) {
560
                if (!strstr($methodName, '__')) {
561
                    $method = 'any';
562
                    foreach (explode('|', RouterRequest::$validMethods) as $m) {
563
                        if (stripos($methodName, strtolower($m), 0) === 0) {
564
                            $method = strtolower($m);
565
                            break;
566
                        }
567
                    }
568
569
                    $methodVar = lcfirst(preg_replace('/' . $method . '/i', '', $methodName, 1));
570
                    $methodVar = strtolower(preg_replace('%([a-z]|[0-9])([A-Z])%', '\1-\2', $methodVar));
571
                    $r = new ReflectionMethod($controller, $methodName);
572
                    $endpoints = [];
573
                    foreach ($r->getParameters() as $param) {
574
                        $pattern = ':any';
575
                        $typeHint = $param->hasType() ? $param->getType()->getName() : null;
576
                        if (in_array($typeHint, ['int', 'bool'])) {
577
                            $pattern = ':id';
578
                        } elseif (in_array($typeHint, ['string', 'float'])) {
579
                            $pattern = ':slug';
580
                        } elseif ($typeHint === null) {
581
                            $pattern = ':any';
582
                        } else {
583
                            continue;
584
                        }
585
                        $endpoints[] = $param->isOptional() ? $pattern . '?' : $pattern;
586
                    }
587
588
                    $value = ($methodVar === $this->mainMethod ? $route : $route . '/' . $methodVar);
589
                    $this->{$method}(
590
                        ($value . '/' . implode('/', $endpoints)),
591
                        $settings,
592
                        ($controller . '@' . $methodName)
0 ignored issues
show
Bug introduced by
Are you sure $controller of type Ballybran\Routing\Router\RouterException|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

592
                        (/** @scrutinizer ignore-type */ $controller . '@' . $methodName)
Loading history...
593
                    );
594
                }
595
            }
596
            unset($r);
597
        }
598
599
        return true;
600
    }
601
602
    /**
603
     * Routes error function. (Closure)
604
     *
605
     * @param $callback
606
     *
607
     * @return void
608
     */
609
    public function error($callback)
610
    {
611
        $this->errorCallback = $callback;
612
    }
613
614
    /**
615
     * Detect Routes Middleware; before or after
616
     *
617
     * @param $middleware
618
     * @param $type
619
     *
620
     * @return void
621
     */
622
    public function runRouteMiddleware($middleware, $type)
623
    {
624
        if ($type === 'before') {
625
            if (!is_null($middleware['group'])) {
626
                $this->routerCommand()->beforeAfter($middleware['group'][$type]);
627
            }
628
            $this->routerCommand()->beforeAfter($middleware[$type]);
629
        } else {
630
            $this->routerCommand()->beforeAfter($middleware[$type]);
631
            if (!is_null($middleware['group'])) {
632
                $this->routerCommand()->beforeAfter($middleware['group'][$type]);
633
            }
634
        }
635
    }
636
637
    /**
638
     * Display all Routes.
639
     *
640
     * @return void
641
     */
642
    public function getList()
643
    {
644
        echo '<pre>';
645
        var_dump($this->getRoutes());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($this->getRoutes()) looks like debug code. Are you sure you do not want to remove it?
Loading history...
646
        echo '</pre>';
647
        die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
648
    }
649
650
    /**
651
     * Get all Routes
652
     *
653
     * @return mixed
654
     */
655
    public function getRoutes()
656
    {
657
        return $this->routes;
658
    }
659
660
    /**
661
     * Throw new Exception for Router Error
662
     *
663
     * @param $message
664
     *
665
     * @return RouterException
666
     * @throws
667
     */
668
    public function exception($message = '')
669
    {
670
        return new RouterException($message);
671
    }
672
673
    /**
674
     * RouterCommand class
675
     *
676
     * @return RouterCommand
677
     */
678
    public function routerCommand()
679
    {
680
        return RouterCommand::getInstance($this->baseFolder, $this->paths, $this->namespaces);
681
    }
682
683
    /**
684
     * Cache all routes
685
     *
686
     * @return bool
687
     * @throws Exception
688
     */
689
    public function cache()
690
    {
691
        foreach ($this->getRoutes() as $key => $r) {
692
            if (!is_string($r['callback'])) {
693
                throw new Exception(sprintf('Routes cannot contain a Closure/Function callback while caching.'));
694
            }
695
        }
696
697
        $cacheContent = '<?php return ' . var_export($this->getRoutes(), true) . ';' . PHP_EOL;
698
        if (false === file_put_contents($this->cacheFile, $cacheContent)) {
699
            throw new Exception(sprintf('Routes cache file could not be written.'));
700
        }
701
702
        return true;
703
    }
704
705
    /**
706
     * Set paths and namespaces for Controllers and Middlewares.
707
     *
708
     * @param array $params
709
     *
710
     * @return void
711
     */
712
    protected function setPaths($params)
713
    {
714
        if (empty($params)) {
715
            return;
716
        }
717
718
        if (isset($params['paths']) && $paths = $params['paths']) {
719
            $this->paths['controllers'] = isset($paths['controllers'])
720
                ? trim($paths['controllers'], '/')
721
                : $this->paths['controllers'];
722
723
            $this->paths['middlewares'] = isset($paths['middlewares'])
724
                ? trim($paths['middlewares'], '/')
725
                : $this->paths['middlewares'];
726
        }
727
728
        if (isset($params['namespaces']) && $namespaces = $params['namespaces']) {
729
            $this->namespaces['controllers'] = isset($namespaces['controllers'])
730
                ? trim($namespaces['controllers'], '\\') . '\\'
731
                : '';
732
733
            $this->namespaces['middlewares'] = isset($namespaces['middlewares'])
734
                ? trim($namespaces['middlewares'], '\\') . '\\'
735
                : '';
736
        }
737
738
        if (isset($params['base_folder'])) {
739
            $this->baseFolder = rtrim($params['base_folder'], '/');
740
        }
741
742
        if (isset($params['main_method'])) {
743
            $this->mainMethod = $params['main_method'];
744
        }
745
746
        $this->cacheFile = isset($params['cache']) ? $params['cache'] : realpath(__DIR__ . '/../cache.php');
747
    }
748
749
    /**
750
     * @param $controller
751
     *
752
     * @return RouterException|mixed
753
     */
754
    protected function resolveClass($controller)
755
    {
756
        $controller = str_replace(['\\', '.'], '/', $controller);
757
        $controller = trim(
758
            preg_replace(
759
                '/' . str_replace('/', '\\/', $this->paths['controllers']) . '/i',
760
                '', $controller,
761
                1
762
            ),
763
            '/'
764
        );
765
        $file = realpath(rtrim($this->paths['controllers'], '/') . '/' . $controller . '.php');
766
767
        if (!file_exists($file)) {
768
            return $this->exception($controller . ' class is not found!');
769
        }
770
771
        $controller = $this->namespaces['controllers'] . str_replace('/', '\\', $controller);
772
        if (!class_exists($controller)) {
773
            require $file;
774
        }
775
776
        return $controller;
777
    }
778
779
    /**
780
     * Load Cache file
781
     *
782
     * @return bool
783
     */
784
    protected function loadCache()
785
    {
786
        if (file_exists($this->cacheFile)) {
787
            $this->routes = require $this->cacheFile;
788
            $this->cacheLoaded = true;
789
            return true;
790
        }
791
792
        return false;
793
    }
794
795
    /**
796
     * Add new Route and it's settings
797
     *
798
     * @param $uri
799
     * @param $method
800
     * @param $callback
801
     * @param $settings
802
     *
803
     * @return void
804
     */
805
    private function addRoute($uri, $method, $callback, $settings)
806
    {
807
        $groupItem = count($this->groups) - 1;
808
        $group = '';
809
        if ($groupItem > -1) {
810
            foreach ($this->groups as $key => $value) {
811
                $group .= $value['route'];
812
            }
813
        }
814
815
        $path = dirname($_SERVER['PHP_SELF']);
816
        $path = $path === '/' || strpos($this->runningPath, $path) !== 0 ? '' : $path;
817
818
        if (strstr($path, 'index.php')) {
819
            $data = implode('/', explode('/', $path));
820
            $path = str_replace($data, '', $path);
821
        }
822
823
        $route = $path . $group . '/' . trim($uri, '/');
824
        $route = rtrim($route, '/');
825
        if ($route === $path) {
826
            $route .= '/';
827
        }
828
829
        $routeName = is_string($callback)
830
            ? strtolower(preg_replace(
831
                '/[^\w]/i', '.', str_replace($this->namespaces['controllers'], '', $callback)
832
            ))
833
            : null;
834
        $data = [
835
            'route' => $this->clearRouteName($route),
836
            'method' => strtoupper($method),
837
            'callback' => $callback,
838
            'name' => isset($settings['name']) ? $settings['name'] : $routeName,
839
            'before' => isset($settings['before']) ? $settings['before'] : null,
840
            'after' => isset($settings['after']) ? $settings['after'] : null,
841
            'group' => $groupItem === -1 ? null : $this->groups[$groupItem],
842
        ];
843
        array_push($this->routes, $data);
844
    }
845
846
    /**
847
     * Run Route Command; Controller or Closure
848
     *
849
     * @param $command
850
     * @param $params
851
     *
852
     * @return void
853
     */
854
    private function runRouteCommand($command, $params = null)
855
    {
856
        $this->routerCommand()->runRoute($command, $params);
857
    }
858
859
    /**
860
     * Routes Group endpoint
861
     *
862
     * @return void
863
     */
864
    private function endGroup()
865
    {
866
        array_pop($this->groups);
867
    }
868
869
    /**
870
     * @param string $route
871
     *
872
     * @return string
873
     */
874
    private function clearRouteName($route = '')
875
    {
876
        $route = trim(str_replace('//', '/', $route), '/');
877
        return $route === '' ? '/' : "/{$route}";
878
    }
879
}
880