Dispatcher   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 18
lcom 1
cbo 6
dl 0
loc 144
ccs 0
cts 50
cp 0
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A dispatcher() 0 17 3
A createCachedRoute() 0 10 3
A cachedDispatcher() 0 8 2
A addRoutes() 0 10 2
A setRouteClosures() 0 21 4
A runDispatcher() 0 5 1
A getRouteData() 0 16 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Selami\Router;
5
6
use FastRoute;
7
use Selami\Router\Exceptions\InvalidCacheFileException;
8
9
class Dispatcher
10
{
11
12
    /**
13
     * Routes array to be registered.
14
     * Some routes may have aliases to be used in templating system
15
     * Route item can be defined using array key as an alias key.
16
     * Each route item is an array has items respectively: Request Method, Request Uri, Controller/Action, Return Type.
17
     *
18
     * @var array
19
     */
20
    private $routes;
21
22
23
    /**
24
     * Default return type if not noted in the $routes
25
     *
26
     * @var int
27
     */
28
    private $defaultReturnType;
29
30
    /**
31
     * @var null|string
32
     */
33
    private $cacheFile;
34
35
    /**
36
     * @var array
37
     */
38
    private $routerClosures = [];
39
40
    private static $dispatchResults = [
41
        FastRoute\Dispatcher::METHOD_NOT_ALLOWED => 405,
42
        FastRoute\Dispatcher::FOUND => 200 ,
43
        FastRoute\Dispatcher::NOT_FOUND => 404
44
    ];
45
46
    public function __construct(array $routes, int $defaultReturnType, ?string  $cacheFile)
47
    {
48
        $this->routes = $routes;
49
        $this->defaultReturnType = $defaultReturnType;
50
        $this->cacheFile = $cacheFile;
51
    }
52
53
    /*
54
     * Dispatch against the provided HTTP method verb and URI.
55
     */
56
    public function dispatcher() : FastRoute\Dispatcher
57
    {
58
        $this->setRouteClosures();
59
        if ($this->cacheFile !== null && file_exists($this->cacheFile)) {
60
            return $this->cachedDispatcher();
61
        }
62
        /**
63
         * @var \FastRoute\RouteCollector $routeCollector
64
         */
65
        $routeCollector = new FastRoute\RouteCollector(
66
            new FastRoute\RouteParser\Std,
67
            new FastRoute\DataGenerator\GroupCountBased
68
        );
69
        $this->addRoutes($routeCollector);
70
        $this->createCachedRoute($routeCollector);
71
        return new FastRoute\Dispatcher\GroupCountBased($routeCollector->getData());
72
    }
73
74
    private function createCachedRoute($routeCollector) : void
75
    {
76
        if ($this->cacheFile !== null && !file_exists($this->cacheFile)) {
77
            /**
78
             * @var FastRoute\RouteCollector $routeCollector
79
             */
80
            $dispatchData = $routeCollector->getData();
81
            file_put_contents($this->cacheFile, '<?php return ' . var_export($dispatchData, true) . ';', LOCK_EX);
82
        }
83
    }
84
85
    private function cachedDispatcher() : FastRoute\Dispatcher\GroupCountBased
86
    {
87
        $dispatchData = require $this->cacheFile;
88
        if (!is_array($dispatchData)) {
89
            throw new InvalidCacheFileException('Invalid cache file "' . $this->cacheFile . '"');
90
        }
91
        return new FastRoute\Dispatcher\GroupCountBased($dispatchData);
92
    }
93
94
    /*
95
     * Define Closures for all routes that returns controller info to be used.
96
     */
97
    private function addRoutes(FastRoute\RouteCollector $route) : void
98
    {
99
        $routeIndex=0;
100
        foreach ($this->routes as $definedRoute) {
101
            $definedRoute[3] = $definedRoute[3] ?? $this->defaultReturnType;
102
            $routeName = 'routeClosure'.$routeIndex;
103
            $route->addRoute(strtoupper($definedRoute[0]), $definedRoute[1], $routeName);
104
            $routeIndex++;
105
        }
106
    }
107
108
    private function setRouteClosures() : void
109
    {
110
        $routeIndex=0;
111
        foreach ($this->routes as $definedRoute) {
112
            $definedRoute[3] = $definedRoute[3] ?? $this->defaultReturnType;
113
            $routeName = 'routeClosure'.$routeIndex;
114
            $this->routerClosures[$routeName]= function ($uriArguments) use ($definedRoute) {
115
                $returnType = ($definedRoute[3] >=1 && $definedRoute[3] <=7) ? $definedRoute[3]
116
                    : $this->defaultReturnType;
117
                return  [
118
                    'status' => 200,
119
                    'requestMethod' => $definedRoute[0],
120
                    'controller' => $definedRoute[2],
121
                    'returnType' => $returnType,
122
                    'pattern' => $definedRoute[1],
123
                    'uriArguments'=> $uriArguments
124
                ];
125
            };
126
            $routeIndex++;
127
        }
128
    }
129
130
    public function runDispatcher(array $routeInfo) : Route
131
    {
132
        return  $this->getRouteData($routeInfo)
133
            ->withStatusCode(self::$dispatchResults[$routeInfo[0]]);
134
    }
135
136
    private function getRouteData(array $routeInfo) : Route
137
    {
138
        if ($routeInfo[0] === FastRoute\Dispatcher::FOUND) {
139
            [$dispatcher, $handler, $vars] = $routeInfo;
140
            $routeParameters =  $this->routerClosures[$handler]($vars);
141
            return new Route(
142
                $routeParameters['requestMethod'],
143
                $routeParameters['pattern'],
144
                $routeParameters['status'],
145
                $routeParameters['returnType'],
146
                $routeParameters['controller'],
147
                $routeParameters['uriArguments']
148
            );
149
        }
150
        return new Route('GET', '/', 200, 1, 'main', []);
151
    }
152
}
153