Passed
Push — master ( d2743c...7dbeaf )
by Nícollas
01:44
created

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