Completed
Push — master ( a8c537...a10276 )
by Mehmet
02:25
created

Router   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 91.67%

Importance

Changes 0
Metric Value
wmc 18
lcom 1
cbo 1
dl 0
loc 249
ccs 55
cts 60
cp 0.9167
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A extractFolder() 0 10 3
A dispatcher() 0 15 1
A __construct() 0 14 2
B add() 0 18 6
A addRoutes() 0 11 2
A getRoute() 0 10 1
A runDispatcher() 0 16 1
A getRouteData() 0 13 2
1
<?php
2
/**
3
 * Selami Router
4
 * PHP version 7+
5
 *
6
 * @license https://github.com/selamiphp/router/blob/master/LICENSE (MIT License)
7
 * @link https://github.com/selamiphp/router
8
 */
9
10
declare(strict_types = 1);
11
12
namespace Selami;
13
14
use FastRoute;
15
use InvalidArgumentException;
16
17
/**
18
 * Router
19
 *
20
 * This class is responsible for registering route objects,
21
 * determining aliases if available and finding requested route
22
 */
23
final class Router
24
{
25
    /**
26
     * routes array to be registered.
27
     * Some routes may have aliases to be used in templating system
28
     * Route item can be defined using array key as an alias key.
29
     * @var array
30
     */
31
    private $routes = [];
32
33
    /**
34
     * aliases array to be registered.
35
     * Each route item is an array has items respectively : Request Method, Request Uri, Controller/Action, Return Type.
36
     * @var array
37
     */
38
    private $aliases = [];
39
40
    /**
41
     * HTTP request Method
42
     * @var string
43
     */
44
    private $method;
45
46
    /**
47
     * Request Uri
48
     * @var string
49
     */
50
    private $requestedPath;
51
52
    /**
53
     * Default return type if not noted in the $routes
54
     * @var string
55
     */
56
    private $defaultReturnType;
57
58
59
    /**
60
     * Translation array.
61
     * Make sures about return type.
62
     * @var array
63
     */
64
    private static $translations = [
65
        'h'     => 'html',
66
        'html'  => 'html',
67
        'r'     => 'redirect',
68
        'redirect' => 'redirect',
69
        'j'     => 'json',
70
        'json'  => 'json',
71
        't'     => 'text',
72
        'text'  => 'text',
73
        'd'     => 'download',
74
        'download'  => 'download'
75
    ];
76
77
    /**
78
     * Valid Request Methods array.
79
     * Make sures about requested methods.
80
     * @var array
81
     */
82
    private static $validRequestMethods = [
83
        'GET',
84
        'OPTIONS',
85
        'HEAD',
86
        'POST',
87
        'PUT',
88
        'DELETE',
89
        'PATCH'
90
    ];
91
92
93
    /**
94
     * Valid Request Methods array.
95
     * Make sures about return type.
96
     * @var array
97
     */
98
    private static $validReturnTypes = [
99
        'html',
100
        'json',
101
        'text',
102
        'redirect',
103
        'download'
104
    ];
105
106
    /**
107
     * Router constructor.
108
     * Create new router.
109
     *
110
     * @param array $routes
0 ignored issues
show
Bug introduced by
There is no parameter named $routes. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
111
     * @param string $defaultReturnType
112
     * @param string $method
113
     * @param string $requestedPath
114
     * @param string $folder
115
     * @throws InvalidArgumentException
116
     */
117 7
    public function __construct(
118
        string $defaultReturnType,
119
        string $method,
120
        string $requestedPath,
121
        string $folder = ''
122
    ) {
123 7
        if (!in_array($method, self::$validRequestMethods, true)) {
124
            $message = sprintf('%s is nat valid Http request method.', $method);
125
            throw new InvalidArgumentException($message);
126
        }
127 7
        $this->method   = $method;
128 7
        $this->requestedPath     = $this->extractFolder($requestedPath, $folder);
129 7
        $this->defaultReturnType = self::$translations[$defaultReturnType] ?? self::$defaultReturnType[0];
130 7
    }
131
132
    /**
133
     * Remove sub folder from requestedPath if defined
134
     * @param string $requestPath
135
     * @param string $folder
136
     * @return string
137
     */
138 7
    private function extractFolder(string $requestPath, string $folder)
139
    {
140 7
        if (!empty($folder)) {
141 1
            $requestPath = '/' . trim(preg_replace('#^/' . $folder . '#msi', '/', $requestPath), '/');
142
        }
143 7
        if ($requestPath === '') {
144 1
            $requestPath = '/';
145
        }
146 7
        return $requestPath;
147
    }
148
149
    /**
150
     * add route to routes list
151
     * @param string|array requestMethods
152
     * @param string $route
153
     * @param string $action
154
     * @param string $returnType
155
     * @param string $alias
156
     * @return string
157
     * @throws InvalidArgumentException
158
     */
159 7
    public function add($requestMethods, string $route, string $action, string $returnType=null, string $alias=null)
160
    {
161 7
        $requestMethodParameterType = gettype($requestMethods);
162 7
        if (!in_array($requestMethodParameterType, ['array', 'string'], true)) {
163
            $message = sprintf(
164
                'Request method must be either string or array but %s given.',
165
                $requestMethodParameterType);
166
            throw new InvalidArgumentException($message);
167
        }
168 7
        $requestMethodsGiven = is_array($requestMethods) ? (array) $requestMethods : [0 => $requestMethods];
169 7
        $returnType = $returnType === null ? $this->defaultReturnType: self::$validReturnTypes[$returnType]?? $this->defaultReturnType;
170 7
        foreach ($requestMethodsGiven as $requestMethod) {
171 7
            if ($alias !== null) {
172 7
                $this->aliases[$alias] = $route;
173
            }
174 7
            $this->routes[] = [strtoupper($requestMethod), $route, $action, $returnType];
175
        }
176 7
    }
177
178
    /**
179
     * Dispatch against the provided HTTP method verb and URI.
180
     * @return array
181
     */
182 3
    private function dispatcher()
183
    {
184
        $options = [
185 3
            'routeParser'   => 'FastRoute\\RouteParser\\Std',
186
            'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
187
            'dispatcher'    => 'FastRoute\\Dispatcher\\GroupCountBased',
188
            'routeCollector' => 'FastRoute\\RouteCollector',
189
        ];
190
        /** @var RouteCollector $routeCollector */
191 3
        $routeCollector = new $options['routeCollector'](
192 3
            new $options['routeParser'], new $options['dataGenerator']
193
        );
194 3
        $this->addRoutes($routeCollector);
195 3
        return new $options['dispatcher']($routeCollector->getData());
196
    }
197
198
    /**
199
     * Define Closures for all routes that returns controller info to be used.
200
     * @param FastRoute\RouteCollector $route
201
     */
202 3
    private function addRoutes(FastRoute\RouteCollector $route)
203
    {
204 3
        foreach ($this->routes as $definedRoute) {
205 3
            $definedRoute[3] = $definedRoute[3] ?? $this->defaultReturnType;
206 3
            $route->addRoute(strtoupper($definedRoute[0]), $definedRoute[1], function ($args) use ($definedRoute) {
207 1
                list(,,$controller, $returnType) = $definedRoute;
208 1
                $returnType = Router::$translations[$returnType] ?? $this->defaultReturnType;
209 1
                return  ['controller' => $controller, 'returnType'=> $returnType, 'args'=> $args];
210 3
            });
211
        }
212 3
    }
213
214
215
216
    /**
217
     * Get router data that includes route info and aliases
218
     */
219 3
    public function getRoute()
220
    {
221 3
        $dispatcher = $this->dispatcher();
222 3
        $routeInfo  = $dispatcher->dispatch($this->method, $this->requestedPath);
223
        $routerData = [
224 3
            'route'     => $this->runDispatcher($routeInfo),
225 3
            'aliases'   => $this->aliases
226
        ];
227 3
        return $routerData;
228
    }
229
230
231
    /**
232
     * Get route info for requested uri
233
     * @param array $routeInfo
234
     * @return array $routerData
235
     */
236 3
    private function runDispatcher(array $routeInfo)
237
    {
238 3
        $routeData = $this->getRouteData($routeInfo);
239
        $dispatchResults = [
240 3
            FastRoute\Dispatcher::METHOD_NOT_ALLOWED => [
241
                'status' => 405
242 3
            ],
243 3
            FastRoute\Dispatcher::FOUND => [
244
                'status'  => 200
245
            ],
246 3
            FastRoute\Dispatcher::NOT_FOUND => [
247
                'status' => 404
248
            ]
249
        ];
250 3
        return array_merge($routeData, $dispatchResults[$routeInfo[0]]);
251
    }
252
253
    /**
254
     * Get routeData according to dispatcher's results
255
     * @param array $routeInfo
256
     * @return array
257
     */
258 3
    private function getRouteData(array $routeInfo)
259
    {
260 3
        if ($routeInfo[0] === FastRoute\Dispatcher::FOUND) {
261 1
            list(, $handler, $vars) = $routeInfo;
262 1
            return $handler($vars);
263
        }
264
        return [
265 2
            'status'        => 200,
266
            'returnType'    => 'html',
267
            'definedRoute'  => null,
268
            'args'          => []
269
        ];
270
    }
271
}
272