Passed
Push — master ( c1faee...1058d7 )
by Nícollas
01:48
created

RouteCollection::resolveRequestMethod()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 2
nop 0
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace MinasRouter\Router;
4
5
use MinasRouter\Router\RouteGroups;
6
use MinasRouter\Traits\RouterHelpers;
7
use MinasRouter\Traits\RouteManagement;
8
use MinasRouter\Exceptions\NotFoundException;
9
use MinasRouter\Exceptions\BadMethodCallException;
10
use MinasRouter\Exceptions\MethodNotAllowedException;
11
use MinasRouter\Router\Middlewares\MiddlewareCollection;
12
use MinasRouter\Exceptions\BadMiddlewareExecuteException;
13
14
class RouteCollection
15
{
16
    use RouteManagement, RouterHelpers;
0 ignored issues
show
Bug introduced by
The trait MinasRouter\Traits\RouterHelpers requires the property $prefix which is not provided by MinasRouter\Router\RouteCollection.
Loading history...
17
18
    protected $actionSeparator;
19
20
    protected $currentUri;
21
22
    protected $currentGroup;
23
24
    protected $baseUrl;
25
26
    protected $currentRoute;
27
28
    protected $requestMethod;
29
30
    protected $httpCodes = [
31
        "badRequest" => 400,
32
        "notAllowed" => 403,
33
        "notFound" => 404,
34
        "methodNotAllowed" => 405,
35
        "notImplemented" => 501,
36
        "redirect" => 302
37
    ];
38
39
    protected $routes = [
40
        "GET" => [],
41
        "POST" => [],
42
        "PUT" => [],
43
        "PATCH" => [],
44
        "DELETE" => [],
45
        "REDIRECT" => []
46
    ];
47
48
    protected $formSpoofingMethods = ["PUT", "PATCH", "DELETE"];
49
50
    public function __construct(String $separator, String $baseUrl)
51
    {
52
        $this->actionSeparator = $separator;
53
        $this->baseUrl = $baseUrl;
54
        $this->currentUri = filter_input(INPUT_GET, "route", FILTER_DEFAULT) ?? "/";
55
    }
56
57
    /**
58
     * Method responsible for defining the 
59
     * group of current routes.
60
     * 
61
     * @param null|\MinasRouter\Router\RouteGroups $group = null
62
     * 
63
     * @return void
64
     */
65
    public function defineGroup(?RouteGroups $group = null): void
66
    {
67
        $this->currentGroup = $group;
68
    }
69
70
    /**
71
     * Method responsible for adding a
72
     * route to an http method.
73
     * 
74
     * @param string $method
75
     * @param string $uri
76
     * @param array|string|\Closure $callback
77
     * 
78
     * @return \MinasRouter\Router\RouteManager
79
     */
80
    public function addRoute(String $method, $uri, $callback)
81
    {
82
        $uri = $this->resolveRouterUri($uri);
83
84
        if (array_key_exists($method, $this->routes)) {
85
            return $this->routes[$method][$uri] = $this->addRouter($uri, $callback);
86
        }
87
    }
88
89
    /**
90
     * Method responsible for adding the same
91
     * route in more than one http method.
92
     * 
93
     * @param string $uri
94
     * @param array|string|\Closure $callback
95
     * @param null|array $methods
96
     * 
97
     * @return \MinasRouter\Router\RouteManager
98
     */
99
    public function addMultipleHttpRoutes(String $uri, $callback, ?array $methods = null)
100
    {
101
        if (!$methods) {
102
            $methods = array_keys($this->routes);
103
        }
104
105
        $methods = array_map("strtoupper", $methods);
106
107
        array_map(function ($method) use ($uri, $callback) {
108
            $this->routes[$method][$uri] = $this->addRouter($uri, $callback);
109
        }, $methods);
110
    }
111
112
    /**
113
     * Method responsible for adding a redirect route.
114
     * 
115
     * @param string $uri
116
     * @param string $redirect
117
     * @param int $httpCode
118
     * 
119
     * @return void
120
     */
121
    public function addRedirectRoute(String $uri, String $redirect, Int $httpCode): void
122
    {
123
        $uri = $this->resolveRouterUri($uri);
124
125
        $this->routes["REDIRECT"][$uri] = $this->redirectRouterData($redirect, $httpCode);
126
    }
127
128
    /**
129
     * Method responsible for handling method
130
     * calls that do not exist in the class.
131
     * 
132
     * @param string $method
133
     * @param array $arguments
134
     * 
135
     * @return void
136
     */
137
    public function __call($method, $arguments)
138
    {
139
        $this->throwException(
140
            "badRequest",
141
            BadMethodCallException::class,
142
            "Method [%s::%s] doesn't exist.",
143
            static::class,
144
            $method
145
        );
146
    }
147
148
    /**
149
     * Method responsible for returning a route
150
     * by the name attribute.
151
     * 
152
     * @param string $routeName
153
     * @param null|string $httpMethod = null
154
     * 
155
     * @return \MinasRouter\Router\RouteManager|null
156
     */
157
    public function getByName(String $routeName, $httpMethod = null): ?RouteManager
158
    {
159
        $routes = $this->routes;
160
        $httpMethod = !$httpMethod ?: strtoupper($httpMethod);
161
162
        unset($routes["REDIRECT"]);
163
164
        if ($httpMethod && isset($this->routes[$httpMethod])) {
165
            $routes = $this->routes[$httpMethod];
166
        }
167
168
        if (!is_array($routes)) return null;
169
170
        $soughtRoute = null;
171
172
        foreach ($routes as $verb) {
173
            if (!$this->instanceOf($verb, RouteManager::class)) {
174
                foreach ($verb as $route) {
175
                    if ($route->getName() === $routeName) {
176
                        $soughtRoute = $route;
177
                        break;
178
                    }
179
                }
180
            } else {
181
                if ($verb->getName() === $routeName) {
182
                    $soughtRoute = $verb;
183
                    break;
184
                }
185
            }
186
        }
187
188
        return $soughtRoute;
189
    }
190
191
    /**
192
     * Method responsible for verifying if the
193
     * object is an instance of class.
194
     * 
195
     * @param mixed $object
196
     * 
197
     * @return bool
198
     */
199
    protected function instanceOf($object, $class)
200
    {
201
        return is_a($object, $class);
202
    }
203
204
    /**
205
     * Method responsible for redirecting to an
206
     * existing route or a uri.
207
     * 
208
     * @param object|array $route
209
     * @param bool $permanent = false
210
     */
211
    protected function redirectRoute(array $routes, $permanent = false)
212
    {
213
        $redirectRoute = $this->baseUrl;
214
215
        [$routeObject, $route] = $routes;
216
217
        if ($this->instanceOf($routeObject, RouteManager::class)) {
218
            $redirectRoute .= rtrim($routeObject->getRoute(), '(\/)?');
219
        } else {
220
            $redirectRoute .= $this->resolveRouterUri($route["redirect"]);
221
        }
222
223
        header("Location: {$redirectRoute}", true, $permanent ? 301 : $route["httpCode"]);
224
        exit();
225
    }
226
227
    /**
228
     * Method responsible for formSpoofing the
229
     * HTTP verbs coming from the form.
230
     * 
231
     * @return null|void
232
     */
233
    protected function resolveRequestMethod()
234
    {
235
        $method = $_SERVER["REQUEST_METHOD"];
236
237
        if (isset($_POST["_method"]) && in_array($_POST["_method"], $this->formSpoofingMethods)) {
238
            $this->requestMethod = $_POST["_method"];
239
            return null;
240
        }
241
242
        $this->requestMethod = $method;
243
    }
244
245
    /**
246
     * Method responsible for listening to browser calls
247
     * and returning the corresponding route.
248
     * 
249
     * @return void
250
     */
251
    public function run(): void
252
    {
253
        $this->currentRoute = null;
254
255
        if (array_key_exists($currentRoute = $this->resolveRouterUri($this->currentUri), $this->routes["REDIRECT"])) {
256
            $route = $this->routes["REDIRECT"][$currentRoute];
257
            $redirectRoute = $this->getByName($route["redirect"]);
258
259
            $this->redirectRoute(
260
                [$redirectRoute, $route],
261
                $route["permanent"]
262
            );
263
        }
264
265
        $this->resolveRequestMethod();
266
267
        if(!isset($this->routes[$this->requestMethod])) {
268
            $this->throwException(
269
                "badRequest",
270
                BadMethodCallException::class,
271
                "The HTTP method [%s] is not supported.",
272
                $this->requestMethod
273
            );
274
        }
275
276
        foreach ($this->routes[$this->requestMethod] as $route) {
277
            if (preg_match("~^" . $route->getRoute() . "$~", $this->currentUri)) {
278
                $this->currentRoute = $route;
279
            }
280
        }
281
282
        $this->dispatchRoute();
283
    }
284
285
    /**
286
     * Method responsible for performing
287
     * route actions.
288
     * 
289
     * @return null|\Closure
290
     */
291
    protected function dispatchRoute(): ?\Closure
292
    {
293
        if (!$route = $this->currentRoute) {
294
            $this->setHttpCode($this->httpCodes["notFound"]);
295
296
            $this->throwException(
297
                "notFound",
298
                NotFoundException::class,
299
                "Route [%s] with method [%s] not found.",
300
                $_SERVER["REQUEST_URI"],
301
                $this->requestMethod
302
            );
303
        }
304
305
        $this->executeMiddlewares($route);
306
307
        [$controller, $method] = $route->getCompleteAction();
308
309
        if ($this->instanceOf($method, \Closure::class)) {
310
            $this->setHttpCode();
311
312
            return call_user_func($route->getAction(), ...$route->closureReturn());
313
        }
314
315
        if (!class_exists($controller)) {
316
            $this->setHttpCode($this->httpCodes["badRequest"]);
317
318
            $this->throwException(
319
                "badRequest",
320
                BadMethodCallException::class,
321
                "Class [%s::%s] doesn't exist.",
322
                $controller,
323
                $method
324
            );
325
        }
326
327
        $obController = new $controller;
328
329
        if (!method_exists($obController, $method)) {
330
            $this->setHttpCode($this->httpCodes["methodNotAllowed"]);
331
332
            $this->throwException(
333
                "methodNotAllowed",
334
                MethodNotAllowedException::class,
335
                "Method [%s::%s] doesn't exist.",
336
                $controller,
337
                $method
338
            );
339
        }
340
341
        $obController->{$method}(...$route->closureReturn());
342
343
        return null;
344
    }
345
346
    /**
347
     * Method responsible for executing
348
     * the middlewares of the current route.
349
     * 
350
     * @return void
351
     */
352
    protected function executeMiddlewares(RouteManager $route)
353
    {
354
        if ($this->instanceOf($route->getMiddleware(), MiddlewareCollection::class)) {
355
356
            $route->getMiddleware()->setRequest($route->request());
357
358
            if (!$route->getMiddleware()->execute()) {
359
                $this->setHttpCode($this->httpCodes["notFound"]);
360
361
                $this->throwException(
362
                    "notFound",
363
                    BadMiddlewareExecuteException::class,
364
                    "Some middleware has not approved your request."
365
                );
366
            }
367
        }
368
    }
369
370
    /**
371
     * Method responsible for returning an
372
     * http method by slug.
373
     * 
374
     * @param string $slug
375
     * 
376
     * @return null|int
377
     */
378
    protected function getHttpCode(String $slug)
379
    {
380
        if (!isset($this->httpCodes[$slug])) return null;
381
382
        return $this->httpCodes[$slug];
383
    }
384
385
    /**
386
     * Method responsible for rendering
387
     * the http method on the page.
388
     * 
389
     * @param int $code = 200
390
     * 
391
     * @return void
392
     */
393
    protected function setHttpCode(Int $code = 200)
394
    {
395
        http_response_code($code);
396
    }
397
398
    /**
399
     * Method responsible for returning all routes
400
     * from the http method passed in the parameter.
401
     * 
402
     * @param string $method
403
     * 
404
     * @return null|array
405
     */
406
    public function getRoutesOf(String $method)
407
    {
408
        $method = strtoupper($method);
409
410
        if (!isset($this->routes[$method])) return null;
411
412
        return $this->routes[$method];
413
    }
414
}
415