Completed
Push — master ( ec1c77...41662e )
by Mr
02:15
created

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