Passed
Push — master ( 41662e...2ceb11 )
by Mr
02:25
created

Router::getError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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