Passed
Push — master ( 2ceb11...3f204e )
by Mr
02:16
created

Router::getResponse()   A

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
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  RouterInterface
64
     */
65
    public function __call(string $method, array $args): RouterInterface
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
    private function set(array $methods, array $args): RouterInterface
81
    {
82
        list($pattern, $callable) = $args;
83
        $route = new Route($methods, $pattern, $callable, $this->getRequest(), $this->getResponse());
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
     * @return  array
93
     */
94
    public function checkMethods(array $methods): array
95
    {
96
        return array_map(
97
            function($method) {
98
                $method = strtolower($method);
99
100
                try {
101
                    if (!\in_array($method, self::METHODS, false)) {
102
                        throw new Exception("Method \"$method\" is not in allowed list [" . implode(',',
103
                                self::METHODS) . ']');
104
                    }
105
                } catch (Exception $e) {
106
                    // Catch empty because __construct overloaded
107
                }
108
109
                return $method;
110
            },
111
            $methods
112
        );
113
    }
114
115
    /**
116
     * Callable must be only selected methods
117
     *
118
     * @param   array $methods
119
     * @param   string $pattern
120
     * @param   callable|string $callable
121
     * @return  RouterInterface
122
     */
123
    public function map(array $methods, string $pattern, $callable): RouterInterface
124
    {
125
        // Check if method in allowed list
126
        $methods = $this->checkMethods($methods);
127
128
        // Set new route with parameters
129
        $this->set($methods, [$pattern, $callable]);
130
131
        return $this;
132
    }
133
134
    /**
135
     * Any method should be callable
136
     *
137
     * @param   string $pattern
138
     * @param   callable|string $callable
139
     * @return  RouterInterface
140
     */
141
    public function any(string $pattern, $callable): RouterInterface
142
    {
143
        // Set new route with all methods
144
        $this->set(self::METHODS, [$pattern, $callable]);
145
146
        return $this;
147
    }
148
149
    /**
150
     * @param   mixed $request
151
     * @return  RouterInterface
152
     */
153
    public function setRequest($request): RouterInterface
154
    {
155
        $this->_request = $request;
156
        return $this;
157
    }
158
159
    /**
160
     * @return ServerRequestInterface
161
     */
162
    public function getRequest(): ServerRequestInterface
163
    {
164
        return $this->_request;
165
    }
166
167
    /**
168
     * @param   mixed $response
169
     * @return  RouterInterface
170
     */
171
    public function setResponse($response): RouterInterface
172
    {
173
        $this->_response = $response;
174
        return $this;
175
    }
176
177
    /**
178
     * @return ResponseInterface
179
     */
180
    public function getResponse(): ResponseInterface
181
    {
182
        return $this->_response;
183
    }
184
185
    /**
186
     * Overwrite default error class
187
     *
188
     * @param   callable|string $error
189
     * @return  RouterInterface
190
     */
191
    public function setError($error): RouterInterface
192
    {
193
        $this->_error = $error;
194
        return $this;
195
    }
196
197
    /**
198
     * Get error class or closure
199
     *
200
     * @return  callable|object
201
     */
202
    public function getError()
203
    {
204
        $error = $this->_error;
205
        // If string inside then we work on class
206
        if (\is_string($error)) {
207
            $error = new $error();
208
        }
209
        return $error;
210
    }
211
212
    /**
213
     * Add route into the array of routes
214
     *
215
     * @param   RouteInterface $route
216
     * @return  RouterInterface
217
     */
218
    public function setRoute(RouteInterface $route): RouterInterface
219
    {
220
        $regexp = $route->getRegexp();
221
        $this->_routes[$regexp] = $route;
222
        return $this;
223
    }
224
225
    /**
226
     * Find route object by URL nad method
227
     *
228
     * @param   string $uri
229
     * @param   string $method
230
     * @return  array
231
     */
232
    private function checkMatches(string $uri, string $method): array
233
    {
234
        return array_map(
235
            function($regexp, $route) use ($uri, $method) {
236
                $match = preg_match_all($regexp, $uri, $matches);
237
238
                // If something found and method is correct
239
                if ($match && $route->checkMethod($method)) {
240
                    // Set array of variables
241
                    $route->setVariables($matches);
242
                    return $route;
243
                }
244
                return null;
245
            },
246
            // Array with keys
247
            $this->getRoutes(true),
248
            // Array with values
249
            $this->getRoutes()
250
        );
251
    }
252
253
    /**
254
     * Find optimal route from array of routes by regexp and uri
255
     *
256
     * @return  array
257
     */
258
    private function getMatches(): array
259
    {
260
        // Extract URI of current query
261
        $uri = $this->getRequest()->getUri()->getPath();
262
263
        // Extract method of current request
264
        $method = $this->getRequest()->getMethod();
265
        $method = strtolower($method);
266
267
        // Foreach emulation
268
        return $this->checkMatches($uri, $method);
269
    }
270
271
    /**
272
     * Parse URI by Regexp from routes and return single route
273
     *
274
     * @return  RouteInterface
275
     */
276
    public function getRoute(): RouteInterface
277
    {
278
        // Find route by regexp and URI
279
        $matches = $this->getMatches();
280
281
        // Cleanup the array of matches, then reindex array
282
        $matches = array_values(array_filter($matches));
283
284
        // If we have some classes in result of regexp
285
        $result = !empty($matches)
286
            // Take first from matches
287
            ? $matches[0] // Here the Route() object
288
            // Create new object with error inside
289
            : $this->getError();
290
291
        $result->setRequest($this->getRequest());
292
        $result->setResponse($this->getResponse());
293
294
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type callable which is incompatible with the type-hinted return DrMVC\Router\RouteInterface. Consider adding an additional type-check to rule them out.
Loading history...
295
    }
296
297
    /**
298
     * Get all available routes
299
     *
300
     * @param   bool $keys - Return only keys
301
     * @return  array
302
     */
303
    public function getRoutes(bool $keys = false): array
304
    {
305
        return $keys
306
            ? array_keys($this->_routes)
307
            : $this->_routes;
308
    }
309
310
}
311