Passed
Push — master ( fd8988...1427e9 )
by Mehmet
01:40
created

Router::__call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 12
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 9
nc 1
nop 2
crap 2
1
<?php
2
/**
3
 * Selami Router
4
 * PHP version 7.1+
5
 *
6
 * @category Library
7
 * @package  Router
8
 * @author   Mehmet Korkmaz <[email protected]>
9
 * @license  https://github.com/selamiphp/router/blob/master/LICENSE (MIT License)
10
 * @link     https://github.com/selamiphp/router
11
 */
12
13
declare(strict_types = 1);
14
15
namespace Selami;
16
17
use FastRoute;
18
use InvalidArgumentException;
19
use UnexpectedValueException;
20
use RuntimeException;
21
22
/**
23
 * Router
24
 *
25
 * This class is responsible for registering route objects,
26
 * determining aliases if available and finding requested route
27
 */
28
final class Router
29
{
30
    const HTML = 1;
31
    const JSON = 2;
32
    const TEXT = 3;
33
    const XML = 4;
34
    const REDIRECT = 5;
35
    const DOWNLOAD = 6;
36
    const CUSTOM = 7;
37
38
    const OPTIONS = 'OPTIONS';
39
    const HEAD = 'HEAD';
40
    const GET = 'GET';
41
    const POST = 'POST';
42
    const PUT = 'PUT';
43
    const DELETE = 'DELETE';
44
    const PATCH = 'PATCH';
45
46
    /**
47
     * Routes array to be registered.
48
     * Some routes may have aliases to be used in templating system
49
     * Route item can be defined using array key as an alias key.
50
     * Each route item is an array has items respectively: Request Method, Request Uri, Controller/Action, Return Type.
51
     *
52
     * @var array
53
     */
54
    private $routes = [];
55
56
    /**
57
     * Aliases array to be registered.
58
     *
59
     * @var array
60
     */
61
    private $aliases = [];
62
63
    /**
64
     * HTTP request Method
65
     *
66
     * @var string
67
     */
68
    private $method;
69
70
    /**
71
     * Request Uri
72
     *
73
     * @var string
74
     */
75
    private $requestedPath;
76
77
    /**
78
     * Default return type if not noted in the $routes
79
     *
80
     * @var string
81
     */
82
    private $defaultReturnType;
83
84
    /**
85
     * @var null|string
86
     */
87
    private $cachedFile;
88
89
    /**
90
     * Valid Request Methods array.
91
     * Make sures about requested methods.
92
     *
93
     * @var array
94
     */
95
    private static $validRequestMethods = [
96
        'GET',
97
        'OPTIONS',
98
        'HEAD',
99
        'POST',
100
        'PUT',
101
        'DELETE',
102
        'PATCH'
103
    ];
104
105
    /**
106
     * Router constructor.
107
     * Create new router.
108
     *
109
     * @param  int    $defaultReturnType
110
     * @param  string $method
111
     * @param  string $requestedPath
112
     * @param  string $folder
113
     * @param  string $cachedFile
114
     * @throws UnexpectedValueException
115
     */
116
    public function __construct(
117
        int $defaultReturnType,
118
        string $method,
119
        string $requestedPath,
120
        string $folder = '',
121
        ?string $cachedFile = null
122
    ) {
123
        if (!in_array($method, self::$validRequestMethods, true)) {
124
            $message = sprintf('%s is not valid Http request method.', $method);
125
            throw new UnexpectedValueException($message);
126
        }
127
        $this->method = $method;
128
        $this->requestedPath = $this->extractFolder($requestedPath, $folder);
129
        $this->defaultReturnType = ($defaultReturnType >=1 && $defaultReturnType <=7) ? $defaultReturnType : self::HTML;
0 ignored issues
show
Documentation Bug introduced by
The property $defaultReturnType was declared of type string, but $defaultReturnType >= 1 ...ReturnType : self::HTML is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
130
        $this->cachedFile = $cachedFile;
131
    }
132
133
    /**
134
     * Remove sub folder from requestedPath if defined
135
     *
136
     * @param  string $requestPath
137
     * @param  string $folder
138
     * @return string
139
     */
140
    private function extractFolder(string $requestPath, string $folder) : string
141
    {
142
        if (!empty($folder)) {
143
            $requestPath = '/' . trim(preg_replace('#^/' . $folder . '#msi', '/', $requestPath), '/');
144
        }
145
        if ($requestPath === '') {
146
            $requestPath = '/';
147
        }
148
        return $requestPath;
149
    }
150
151
    /**
152
     * Add route to routes list
153
     *
154
     * @param  string|array requestMethods
155
     * @param  string                      $route
156
     * @param  string                      $action
157
     * @param  int                         $returnType
158
     * @param  string                      $alias
159
     * @throws InvalidArgumentException
160
     * @throws UnexpectedValueException
161
     */
162
    public function add(
163
        $requestMethods,
164
        string $route,
165
        string $action,
166
        ?int $returnType = null,
167
        ?string $alias = null
168
    ) : void {
169
    
170
        $requestMethodsGiven = is_array($requestMethods) ? (array) $requestMethods : [0 => $requestMethods];
171
        $returnType = $this->determineReturnType($returnType);
172
        foreach ($requestMethodsGiven as $requestMethod) {
173
            $this->checkRequestMethodParameterType($requestMethod);
174
            $this->checkRequestMethodIsValid($requestMethod);
175
            if ($alias !== null) {
176
                $this->aliases[$alias] = $route;
177
            }
178
            $this->routes[] = [strtoupper($requestMethod), $route, $action, $returnType];
179
        }
180
    }
181
182
    /**
183
     * @param string $method
184
     * @param array  $args
185
     * @throws UnexpectedValueException
186
     * @throws InvalidArgumentException
187
     */
188
    public function __call(string $method, array $args) : void
189
    {
190
        $this->checkRequestMethodIsValid($method);
191
        $defaults = [
192
            null,
193
            null,
194
            $this->defaultReturnType,
195
            null
196
        ];
197
        [$route, $action, $returnType, $alias] = array_merge($args, $defaults);
4 ignored issues
show
Bug introduced by
The variable $route does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $action does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $returnType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $alias does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
198
        $this->add($method, $route, $action, $returnType, $alias);
199
    }
200
201
    /**
202
     * @param int|null $returnType
203
     * @return int
204
     */
205
    private function determineReturnType(?int $returnType) : int
206
    {
207
        if ($returnType === null) {
208
            return $this->defaultReturnType;
209
        }
210
        return ($returnType >=1 && $returnType <=7) ? $returnType : self::HTML;
211
    }
212
213
    /**
214
     * @param string $requestMethod
215
     * Checks if request method is valid
216
     * @throws UnexpectedValueException;
217
     */
218
    private function checkRequestMethodIsValid(string $requestMethod) : void
219
    {
220
        if (!in_array(strtoupper($requestMethod), self::$validRequestMethods, true)) {
221
            $message = sprintf('%s is not valid Http request method.', $requestMethod);
222
            throw new UnexpectedValueException($message);
223
        }
224
    }
225
226
    /**
227
     * @param $requestMethod
228
     * @throws InvalidArgumentException
229
     */
230
    private function checkRequestMethodParameterType($requestMethod) : void
231
    {
232
        $requestMethodParameterType = gettype($requestMethod);
233
        if (!in_array($requestMethodParameterType, ['array', 'string'], true)) {
234
            $message = sprintf(
235
                'Request method must be string or array but %s given.',
236
                $requestMethodParameterType
237
            );
238
            throw new InvalidArgumentException($message);
239
        }
240
    }
241
242
243
    /**
244
     * Get router data that includes route info and aliases
245
     *
246
     * @return array
247
     * @throws RuntimeException
248
     */
249
    public function getRoute() : array
250
    {
251
        $selamiDispatcher = new Dispatcher($this->routes, $this->defaultReturnType, $this->cachedFile);
252
        $dispatcher = $selamiDispatcher->dispatcher();
253
        $routeInfo  = $dispatcher->dispatch($this->method, $this->requestedPath);
254
        $route = $selamiDispatcher->runDispatcher($routeInfo);
255
        $routerData = [
256
            'route'     => $route,
257
            'aliases'   => $this->aliases
258
        ];
259
        return $routerData;
260
    }
261
262
}
263