Router::getRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DrMVC\Router;
4
5
use Psr\Http\Message\ServerRequestInterface;
6
use Psr\Http\Message\ResponseInterface;
7
8
/**
9
 * Class Router
10
 * @package DrMVC\Router
11
 * @method Router options(string $pattern, callable $callable): Router
12
 * @method Router get(string $pattern, callable $callable): Router
13
 * @method Router head(string $pattern, callable $callable): Router
14
 * @method Router post(string $pattern, callable $callable): Router
15
 * @method Router put(string $pattern, callable $callable): Router
16
 * @method Router delete(string $pattern, callable $callable): Router
17
 * @method Router trace(string $pattern, callable $callable): Router
18
 * @method Router connect(string $pattern, callable $callable): Router
19
 * @since 3.0
20
 */
21
class Router implements RouterInterface, MethodsInterface
22
{
23
    /**
24
     * Array with all available routes
25
     * @var array
26
     */
27
    private $_routes = [];
28
29
    /**
30
     * Class with error inside
31
     * @var callable|string
32
     */
33
    private $_error = Error::class;
34
35
    /**
36
     * @var ServerRequestInterface
37
     */
38
    private $_request;
39
40
    /**
41
     * @var ResponseInterface
42
     */
43
    private $_response;
44
45
    /**
46
     * Router constructor.
47
     *
48
     * @param   ServerRequestInterface $request
49
     * @param   ResponseInterface $response
50
     */
51
    public function __construct(ServerRequestInterface $request, ResponseInterface $response)
52
    {
53
        $this
54
            ->setRequest($request)
55
            ->setResponse($response);
56
    }
57
58
    /**
59
     * Some kind of magic ;) details in header of this class, in 'methods'
60
     *
61
     * @param   string $method
62
     * @param   array $args
63
     * @return  MethodsInterface
64
     */
65
    public function __call(string $method, array $args): MethodsInterface
66
    {
67
        if (\in_array($method, self::METHODS, false)) {
68
            $this->set([$method], $args);
69
        }
70
        return $this;
71
    }
72
73
    /**
74
     * Abstraction of setter
75
     *
76
     * @param   array $methods
77
     * @param   array $args
78
     * @return  RouterInterface
79
     */
80
    public function set(array $methods, array $args): RouterInterface
81
    {
82
        list($pattern, $callable) = $args;
83
        $route = new Route($methods, $pattern, $callable);
84
        $this->setRoute($route);
85
        return $this;
86
    }
87
88
    /**
89
     * Check if passed methods in allowed list
90
     *
91
     * @param   array $methods list of methods for check
92
     * @throws  Exception
93
     * @return  array
94
     */
95
    public function checkMethods(array $methods): array
96
    {
97
        return array_map(
98
            function($method) {
99
                $method = strtolower($method);
100
                if (!\in_array($method, self::METHODS, false)) {
101
                    throw new Exception("Method \"$method\" is not in allowed list [" . implode(',',
102
                            self::METHODS) . ']');
103
                }
104
                return $method;
105
            },
106
            $methods
107
        );
108
    }
109
110
    /**
111
     * Callable must be only selected methods
112
     *
113
     * @param   array $methods
114
     * @param   string $pattern
115
     * @param   callable|string $callable
116
     * @throws  Exception
117
     * @return  MethodsInterface
118
     */
119
    public function map(array $methods, string $pattern, $callable): MethodsInterface
120
    {
121
        // Check if method in allowed list
122
        $methods = $this->checkMethods($methods);
123
124
        // Set new route with parameters
125
        $this->set($methods, [$pattern, $callable]);
126
127
        return $this;
128
    }
129
130
    /**
131
     * Any method should be callable
132
     *
133
     * @param   string $pattern
134
     * @param   callable|string $callable
135
     * @return  MethodsInterface
136
     */
137
    public function any(string $pattern, $callable): MethodsInterface
138
    {
139
        // Set new route with all methods
140
        $this->set(self::METHODS, [$pattern, $callable]);
141
142
        return $this;
143
    }
144
145
    /**
146
     * @param   mixed $request
147
     * @return  RouterInterface
148
     */
149
    public function setRequest($request): RouterInterface
150
    {
151
        $this->_request = $request;
152
        return $this;
153
    }
154
155
    /**
156
     * @return ServerRequestInterface
157
     */
158
    public function getRequest(): ServerRequestInterface
159
    {
160
        return $this->_request;
161
    }
162
163
    /**
164
     * Set PSR-7 response object
165
     *
166
     * @param   mixed $response
167
     * @return  RouterInterface
168
     */
169
    public function setResponse($response): RouterInterface
170
    {
171
        $this->_response = $response;
172
        return $this;
173
    }
174
175
    /**
176
     * Get PSR-7 response object
177
     *
178
     * @return  ResponseInterface
179
     */
180
    public function getResponse(): ResponseInterface
181
    {
182
        return $this->_response;
183
    }
184
185
    /**
186
     * Set error method
187
     *
188
     * @param   callable|string $error
189
     * @return  MethodsInterface
190
     */
191
    public function error($error): MethodsInterface
192
    {
193
        $this->setError($error);
194
        return $this;
195
    }
196
197
    /**
198
     * Overwrite default error class
199
     *
200
     * @param   callable|string $error
201
     * @return  RouterInterface
202
     */
203
    public function setError($error): RouterInterface
204
    {
205
        $this->_error = $error;
206
        return $this;
207
    }
208
209
    /**
210
     * Get error class or closure
211
     *
212
     * @return  RouteInterface
213
     */
214
    public function getError(): RouteInterface
215
    {
216
        return new Route(['get'], 'error', $this->_error);
217
    }
218
219
    /**
220
     * Add route into the array of routes
221
     *
222
     * @param   RouteInterface $route
223
     * @return  RouterInterface
224
     */
225
    public function setRoute(RouteInterface $route): RouterInterface
226
    {
227
        $regexp = $route->getRegexp();
228
        $this->_routes[$regexp] = $route;
229
        return $this;
230
    }
231
232
    /**
233
     * Find route object by URL nad method
234
     *
235
     * @param   string $uri
236
     * @param   string $method
237
     * @return  array
238
     */
239
    private function checkMatches(string $uri, string $method): array
240
    {
241
        return array_map(
242
            function($regexp, $route) use ($uri, $method) {
243
                $match = preg_match_all($regexp, $uri, $matches);
244
245
                // If something found and method is correct
246
                if ($match && $route->checkMethod($method)) {
247
                    // Set array of variables
248
                    $route->setVariables($matches);
249
                    return $route;
250
                }
251
                return null;
252
            },
253
            // Array with keys
254
            $this->getRoutes(true),
255
            // Array with values
256
            $this->getRoutes()
257
        );
258
    }
259
260
    /**
261
     * Find optimal route from array of routes by regexp and uri
262
     *
263
     * @return  array
264
     */
265
    private function getMatches(): array
266
    {
267
        // Extract URI of current query
268
        $uri = $this->getRequest()->getUri()->getPath();
269
270
        // Extract method of current request
271
        $method = $this->getRequest()->getMethod();
272
        $method = strtolower($method);
273
274
        // Foreach emulation
275
        return $this->checkMatches($uri, $method);
276
    }
277
278
    /**
279
     * Parse URI by Regexp from routes and return single route
280
     *
281
     * @return  RouteInterface
282
     */
283
    public function getRoute(): RouteInterface
284
    {
285
        // Find route by regexp and URI
286
        $matches = $this->getMatches();
287
288
        // Cleanup the array of matches, then reindex array
289
        $matches = array_values(array_filter($matches));
290
291
        // If we have some classes in result of regexp
292
        $result = !empty($matches)
293
            // Take first from matches
294
            ? $matches[0] // Here the Route() object
295
            // Create new object with error inside
296
            : $this->getError();
297
298
        return $result;
299
    }
300
301
    /**
302
     * Get all available routes
303
     *
304
     * @param   bool $keys - Return only keys
305
     * @return  array
306
     */
307
    public function getRoutes(bool $keys = false): array
308
    {
309
        return $keys
310
            ? array_keys($this->_routes)
311
            : $this->_routes;
312
    }
313
314
}
315