Router::all()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
/**
4
 * This file is part of the alphaz Framework.
5
 *
6
 * @author Muhammad Umer Farooq (Malik) <[email protected]>
7
 *
8
 * @link https://github.com/alphazframework/framework
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 *  file that was distributed with this source code.
12
 * @since 1.0.0
13
 *
14
 * @license MIT
15
 *
16
 * @note i wrote this file first time by following the course on udemy  => https://www.udemy.com/php-mvc-from-scratch/ , any further/others modification by me
17
 */
18
19
namespace alphaz\Router;
20
21
use alphaz\Cache\Cache;
22
use alphaz\Data\Conversion;
23
use alphaz\http\Request;
24
use alphaz\http\Response;
25
use alphaz\Input\Input;
26
use alphaz\View\View;
27
28
class Router
29
{
30
    /**
31
     * Associative array of routes (the routing table).
32
     *
33
     * @since 1.0.0
34
     *
35
     * @var array
36
     */
37
    protected $routes = [];
38
39
    /**
40
     * Parameters from the matched route.
41
     *
42
     * @since 1.0.0
43
     *
44
     * @var array
45
     */
46
    protected $params = [];
47
48
    /**
49
     * Parameters from the matched route.
50
     *
51
     * @since 1.0.0
52
     *
53
     * @var array
54
     */
55
    private $request;
0 ignored issues
show
introduced by
The private property $request is not used, and could be removed.
Loading history...
56
57
    /**
58
     * Parse the router.
59
     *
60
     * @param string $route The route URL
61
     *
62
     * @since 1.0.0
63
     *
64
     * @return string
65
     */
66
    protected function parseRoutes($route)
67
    {
68
        // Convert the route to a regular expression: escape forward slashes
69
        $route = preg_replace('/\//', '\\/', $route);
70
        // Convert variables e.g. {controller}
71
        $route = preg_replace('/\{([a-z]+)\}/', '(?P<\1>[a-z-]+)', $route);
72
        // Convert variables with custom regular expressions e.g. {id:\d+}
73
        $route = preg_replace('/\{([a-z]+):([^\}]+)\}/', '(?P<\1>\2)', $route);
74
        // Add start and end delimiters, and case insensitive flag
75
        return '/^'.$route.'$/i';
76
    }
77
78
    /**
79
     * Add a route to the routing table.
80
     *
81
     * @param string       $route      The route URL
82
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
83
     * @param string       $methods    request method like GET or GET|POST
84
     * @param string       $middleware Middleare name
85
     * @param array        $redirect   Redirect
86
     * @param mixed        $view       View
87
     *
88
     * @since 1.0.0
89
     *
90
     * @return void
91
     */
92
    public function add($route, $params = '', $methods = 'GET|HEAD', $middleware = '', $redirect = [], $view = null)
93
    {
94
        // Parse the route.
95
        $route = $this->parseRoutes($route);
96
97
        //If array
98
        if (is_array($params)) {
99
            $methodArray = ['method' => $methods];
100
            $params = array_merge($params, $methodArray);
101
            $this->routes[$route] = $params;
102
        } elseif (is_string($params)) {
0 ignored issues
show
introduced by
The condition is_string($params) is always true.
Loading history...
103
            //If string
104
            if (!empty($params)) {
105
                $param = [];
106
                $parts = explode('@', $params);
107
                $param['controller'] = $parts[0];
108
                $param['action'] = $parts[1];
109
                if (isset($parts[2])) {
110
                    $param['namespace'] = $parts[2];
111
                }
112
            }
113
            $param['method'] = $methods;
114
            if (!empty($redirect)) {
115
                $param['redirect'] = true;
116
                $param['to'] = $redirect['to'];
117
                $param['code'] = $redirect['code'];
118
            }
119
120
            if (null != $view) {
121
                $param['view'] = $view['view'];
122
                $param['args'] = $view['args'];
123
            }
124
125
            //If middleware is set then used
126
            (!empty($middleware)) ? $param['middleware'] = $this->addMiddleware($middleware) : $param;
127
            $this->routes[$route] = $param;
128
        } elseif (is_callable($params)) {
129
            (!empty($middleware)) ? $this->routes[$route] = ['callable' => $params, 'method' => $methods, 'middleware' => $this->addMiddleware($middleware)] : $this->routes[$route] = ['callable' => $params, 'method' => $methods];
130
        } else {
131
            throw new \Exception('Wrong agruments given', 500);
132
        }
133
    }
134
135
    /**
136
     * Add the middleware.
137
     *
138
     * @param (string) $name name of middleware
139
     *
140
     * @since 1.0.0
141
     *
142
     * @return object
143
     */
144
    public function addMiddleware($name)
145
    {
146
        //Support middleware in comoponents
147
        //pattern => App\Components\Example\Middleware\@Example;
148
        $parts = explode('@', $name);
149
        if (strcasecmp($parts[0], $name) === 0) {
150
            //If namespace not givent then continue to defaukt
151
            $namespace = "App\Middleware\\";
152
            $middleware = $namespace.$name;
153
        } else {
154
            //If given then continue to provided namespace
155
            $namespace = $parts[0];
156
            $middleware = $namespace.$parts[1];
157
        }
158
        $middleware_object = new $middleware();
159
        if (class_exists($middleware)) {
160
            if (method_exists($middleware_object, 'before') && method_exists($middleware_object, 'after')) {
161
                return $middleware;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $middleware returns the type string which is incompatible with the documented return type object.
Loading history...
162
            } else {
163
                throw new \Exception('Middleware methods before and after not exists', 500);
164
            }
165
        } else {
166
            throw new \Exception("Middleware Class {$middleware} not found", 500);
167
        }
168
    }
169
170
    /**
171
     * Add multiple routes at once from array in the following format:.
172
     *
173
     * @param $routes = [route,param,method,middleware]
0 ignored issues
show
Documentation Bug introduced by
The doc comment = at position 0 could not be parsed: Unknown type name '=' at position 0 in =.
Loading history...
174
     *
175
     * @since 1.0.0
176
     *
177
     * @return void
178
     */
179
    public function addRoutes($routes)
180
    {
181
        if (!is_array($routes) && !$routes instanceof Traversable) {
0 ignored issues
show
Bug introduced by
The type alphaz\Router\Traversable was not found. Did you mean Traversable? If so, make sure to prefix the type with \.
Loading history...
182
            throw new \Exception('Routes should be an array or an instance of Traversable');
183
        }
184
        foreach ($routes as $route) {
185
            call_user_func_array([$this, 'add'], $route);
186
        }
187
    }
188
189
    /**
190
     * Add a route to the routing table as Redirect.
191
     *
192
     * @param string $route The route URL
193
     * @param string $to    Where you want to redirect
194
     * @param string $code  The HTTP code
195
     *
196
     * @since 1.0.0
197
     *
198
     * @return void
199
     */
200
    public function redirect($route, $to, $code = '301')
201
    {
202
        $this->add($route, '', 'GET', '', ['to' => $to, 'code' => $code]);
203
    }
204
205
    /**
206
     * Add a route to the routing table as POST.
207
     *
208
     * @param string       $route      The route URL
209
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
210
     * @param string       $middleware Middleare name
211
     *
212
     * @since 1.0.0
213
     *
214
     * @return void
215
     */
216
    public function post($route, $params, $middleware = '')
217
    {
218
        $this->add($route, $params, 'POST', $middleware);
219
    }
220
221
    /**
222
     * Add a route to the routing table as GET.
223
     *
224
     * @param string       $route      The route URL
225
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
226
     * @param string       $middleware Middleare name
227
     *
228
     * @since 1.0.0
229
     *
230
     * @return void
231
     */
232
    public function get($route, $params, $middleware = '')
233
    {
234
        $this->add($route, $params, 'GET|HEAD', $middleware);
235
    }
236
237
    /**
238
     * Add a route to the routing table as PUT.
239
     *
240
     * @param string       $route      The route URL
241
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
242
     * @param string       $middleware Middleare name
243
     *
244
     * @since 1.0.0
245
     *
246
     * @return void
247
     */
248
    public function put($route, $params, $middleware = '')
249
    {
250
        $this->add($route, $params, 'PUT', $middleware);
251
    }
252
253
    /**
254
     * Add a route to the routing table as PATCH.
255
     *
256
     * @param string       $route      The route URL
257
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
258
     * @param string       $middleware Middleare name
259
     *
260
     * @since 1.0.0
261
     *
262
     * @return void
263
     */
264
    public function patch($route, $params, $middleware = '')
265
    {
266
        $this->add($route, $params, 'PATCH', $middleware);
267
    }
268
269
    /**
270
     * Add a route to the routing table as DELETE.
271
     *
272
     * @param string       $route      The route URL
273
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
274
     * @param string       $middleware Middleare name
275
     *
276
     * @since 1.0.0
277
     *
278
     * @return void
279
     */
280
    public function delete($route, $params, $middleware = '')
281
    {
282
        $this->add($route, $params, 'DELETE', $middleware);
283
    }
284
285
    /**
286
     * Add a route to the routing table as OPTIONS.
287
     *
288
     * @param string       $route      The route URL
289
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
290
     * @param string       $middleware Middleare name
291
     *
292
     * @since 1.0.0
293
     *
294
     * @return void
295
     */
296
    public function options($route, $params, $middleware = '')
297
    {
298
        $this->add($route, $params, 'OPTIONS', $middleware);
299
    }
300
301
    /**
302
     * Add a route to the routing table as TRACE.
303
     *
304
     * @param string       $route      The route URL
305
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
306
     * @param string       $middleware Middleare name
307
     *
308
     * @since 1.0.0
309
     *
310
     * @return void
311
     */
312
    public function trace($route, $params, $middleware = '')
313
    {
314
        $this->add($route, $params, 'TRACE', $middleware);
315
    }
316
317
    /**
318
     * Add a route to the routing table as CONNECT.
319
     *
320
     * @param string       $route      The route URL
321
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
322
     * @param string       $middleware Middleare name
323
     *
324
     * @since 1.0.0
325
     *
326
     * @return void
327
     */
328
    public function connect($route, $params, $middleware = '')
329
    {
330
        $this->add($route, $params, 'CONNECT', $middleware);
331
    }
332
333
    /**
334
     * Add a route to the routing table as all method.
335
     *
336
     * @param string       $route      The route URL
337
     * @param array|string $params     Parameters (controller, action, etc.) or $params Home@index
338
     * @param string       $middleware Middleare name
339
     *
340
     * @since 1.0.0
341
     *
342
     * @return void
343
     */
344
    public function all($route, $params, $middleware = '')
345
    {
346
        $this->add($route, $params, 'GET|HEAD|POST|DELETE|OPTIONS|TRACE|PUT|PATCH|CONNECT', $middleware);
347
    }
348
349
    /**
350
     * Add a route to the routing table as view.
351
     *
352
     * @param string $route      The route URL
353
     * @param string $view       View File
354
     * @param array  $params     Parameters
355
     * @param string $middleware Middleare name
356
     * @param string $methods    request method like GET or GET|POST
357
     *
358
     * @since 1.0.0
359
     *
360
     * @return void
361
     */
362
    public function view($route, $view, $params = [], $method = 'GET', $middleware = '')
363
    {
364
        $this->add($route, '', $method, $middleware, null, ['view' => $view, 'args' => $params]);
365
    }
366
367
    /**
368
     * Get all the routes from the routing table.
369
     *
370
     * @since 1.0.0
371
     *
372
     * @return array
373
     */
374
    public function getRoutes()
375
    {
376
        return $this->routes;
377
    }
378
379
    /**
380
     * Match the route to the routes in the routing table, setting the $params
381
     * property if a route is found.
382
     *
383
     * @param string $url The route URL
384
     *
385
     * @since 1.0.0
386
     *
387
     * @return bool true if a match found, false otherwise
388
     */
389
    public function match($url)
390
    {
391
        foreach ($this->routes as $route => $params) {
392
            if (preg_match($route, $url, $matches)) {
393
                //Check if given method is matched
394
                if ($this->methodMatch($params['method'], null, new Request())) {
395
                    // Get named capture group values
396
                    foreach ($matches as $key => $match) {
397
                        if (is_string($key)) {
398
                            $params[$key] = $match;
399
                        }
400
                    }
401
                    $this->params = $params;
402
403
                    return true;
404
                }
405
            }
406
        }
407
    }
408
409
    /**
410
     * Match the request methods.
411
     *
412
     * @param string $methods       router request method
413
     * @param string $requestMethod Requested method
414
     *
415
     * @since 1.0.0
416
     *
417
     * @return bool
418
     */
419
    public function methodMatch($methods, $requestMethod, Request $request)
420
    {
421
        $match = false;
422
        if ($requestMethod === null) {
0 ignored issues
show
introduced by
The condition $requestMethod === null is always false.
Loading history...
423
            $requestMethod = ($request->getRequestMethod()) ? $request->getRequestMethod() : 'GET';
424
        }
425
        $methods = explode('|', $methods);
426
        foreach ($methods as $method) {
427
            if (strcasecmp($requestMethod, $method) === 0) {
428
                $match = true;
429
                break;
430
            } else {
431
                continue;
432
            }
433
        }
434
        if ($match === true) {
435
            return true;
436
        }
437
438
        return false;
439
    }
440
441
    /**
442
     * Get the currently matched parameters.
443
     *
444
     * @since 1.0.0
445
     *
446
     * @return array
447
     */
448
    public function getParams()
449
    {
450
        return $this->params;
451
    }
452
453
    /**
454
     * Get the current input according to given method.
455
     *
456
     * @since 1.0.0
457
     *
458
     * @return mixed
459
     */
460
    public function getInput(Input $input)
461
    {
462
        $inputData = $input::inputAll();
463
464
        return (new Conversion())::arrayToObject($inputData);
0 ignored issues
show
Bug introduced by
It seems like $inputData can also be of type false; however, parameter $array of alphaz\Data\Conversion::arrayToObject() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

464
        return (new Conversion())::arrayToObject(/** @scrutinizer ignore-type */ $inputData);
Loading history...
465
    }
466
467
    /**
468
     * Dispatch the route, creating the controller object and running the
469
     * action method.
470
     *
471
     * @param string $url The route URL
472
     *
473
     * @since 1.0.0
474
     *
475
     * @return void
476
     */
477
    public function dispatch(Request $request)
478
    {
479
        $url = $request->getQueryString();
480
        $url = $this->RemoveQueryString($url, new Request());
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type array; however, parameter $url of alphaz\Router\Router::RemoveQueryString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

480
        $url = $this->RemoveQueryString(/** @scrutinizer ignore-type */ $url, new Request());
Loading history...
481
        if ($this->match($url)) {
482
            if (isset($this->params['redirect'])) {
483
                \alphaz\Site\Site::redirect($this->params['to'], $this->params['code']);
0 ignored issues
show
Unused Code introduced by
The call to alphaz\Site\Site::redirect() has too many arguments starting with $this->params['code']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

483
                \alphaz\Site\Site::/** @scrutinizer ignore-call */ 
484
                                   redirect($this->params['to'], $this->params['code']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
484
485
                return;
486
            }
487
488
            if (isset($this->params['view'])) {
489
                return View::view($this->params['view'], $this->params['args']);
0 ignored issues
show
Bug introduced by
Are you sure the usage of alphaz\View\View::view($... $this->params['args']) targeting alphaz\View\View::view() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
490
            }
491
            (isset($this->params['middleware'])) ? $this->params['middleware'] = new $this->params['middleware']() : null;
492
            if (!isset($this->params['callable'])) {
493
                $controller = $this->params['controller'];
494
                $controller = $this->convertToStudlyCaps($controller);
495
                $controller = $this->getNamespace().$controller;
496
                if (class_exists($controller)) {
497
                    (isset($this->params['middleware']) && is_object($this->params['middleware'])) ? ( new $this->params['middleware']())->before(new Request(), new Response(), $this->params) : null;
498
                    $controller_object = new $controller($this->params, $this->getInput(new Input()));
499
                    $action = $this->params['action'];
500
                    $action = $this->convertToCamelCase($action);
501
                    if (preg_match('/action$/i', $action) == 0) {
502
                        $controller_object->$action();
503
                        (isset($this->params['middleware']) && is_object($this->params['middleware'])) ? (new $this->params['middleware']())->after(new Request(), new Response(), $this->params) : null;
504
                    } else {
505
                        throw new \Exception("Method $action in controller $controller cannot be called directly - remove the Action suffix to call this method");
506
                    }
507
                } else {
508
                    throw new \Exception("Controller class $controller not found");
509
                }
510
            } else {
511
                (is_object(isset($this->params['middleware']))) ? $this->params['middleware']->before(new Request(), new Response(), $this->params) : null;
0 ignored issues
show
introduced by
The condition is_object(IssetNode) is always false.
Loading history...
512
                call_user_func($this->params['callable'], $this->params);
513
                (is_object(isset($this->params['middleware']))) ? $this->params['middleware']->after(new Request(), new Response(), $this->params) : null;
0 ignored issues
show
introduced by
The condition is_object(IssetNode) is always false.
Loading history...
514
            }
515
        } else {
516
            \alphaz\Component\Router::loadComponents();
517
        }
518
    }
519
520
    /**
521
     * Convert the string with hyphens to StudlyCaps,.
522
     *
523
     * @param string $string The string to convert
524
     *
525
     * @since 1.0.0
526
     *
527
     * @return string
528
     */
529
    protected function convertToStudlyCaps($string)
530
    {
531
        return ucwords(str_replace('-', ' ', $string));
532
    }
533
534
    /**
535
     * Convert the string with hyphens to camelCase,.
536
     *
537
     * @param string $string The string to convert
538
     *
539
     * @since 1.0.0
540
     *
541
     * @return string
542
     */
543
    protected function convertToCamelCase($string)
544
    {
545
        return lcfirst($this->convertToStudlyCaps($string));
546
    }
547
548
    /**
549
     * Remove the query string variables from the URL (if any). As the full.
550
     *
551
     * @param string $url The full URL
552
     *
553
     * @since 1.0.0
554
     *
555
     * @return string The URL with the query string variables removed
556
     */
557
    protected function RemoveQueryString($url, Request $request)
558
    {
559
        if (isset($url) && !empty($url)) {
560
            $parts = explode('&', $url);
561
562
            if (strpos($parts[0], '=') === false) {
563
                $url = $parts[0];
564
            } else {
565
                $url = self::RemoveQueryString($request->getQueryString());
0 ignored issues
show
Bug Best Practice introduced by
The method alphaz\Router\Router::RemoveQueryString() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

565
                /** @scrutinizer ignore-call */ 
566
                $url = self::RemoveQueryString($request->getQueryString());
Loading history...
Bug introduced by
The call to alphaz\Router\Router::RemoveQueryString() has too few arguments starting with request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

565
                /** @scrutinizer ignore-call */ 
566
                $url = self::RemoveQueryString($request->getQueryString());

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
It seems like $request->getQueryString() can also be of type array; however, parameter $url of alphaz\Router\Router::RemoveQueryString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

565
                $url = self::RemoveQueryString(/** @scrutinizer ignore-type */ $request->getQueryString());
Loading history...
566
            }
567
        }
568
569
        return $url;
570
    }
571
572
    /**
573
     * Get the namespace for the controller class. The namespace defined in the
574
     * route parameters is added if present.
575
     *
576
     * @param (string) $namespace valid namespace
577
     *
578
     * @since 1.0.0
579
     *
580
     * @return string The request URL
581
     */
582
    protected function getNamespace($namespace = null)
583
    {
584
        (!array_key_exists('namespace', $this->params)) ? $namespace = 'App\Controllers\\' : $namespace .= $this->params['namespace'].'\\';
585
586
        return $namespace;
587
    }
588
589
    /**
590
     * Parase the url if need.
591
     *
592
     * @since 1.0.0
593
     * @deprecated 3.0.0
594
     *
595
     * @return string The request URL
596
     */
597
    public function parseurl()
598
    {
599
        if (isset($_GET['url'])) {
600
            return $url = explode('/', filter_var(rtrim($_GET['url'], '/'), FILTER_SANITIZE_URL));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $url = explode('/...r\FILTER_SANITIZE_URL)) returns the type string[] which is incompatible with the documented return type string.
Loading history...
Unused Code introduced by
The assignment to $url is dead and can be removed.
Loading history...
601
        }
602
    }
603
604
    /**
605
     * Load routers form cache.
606
     *
607
     * @since 1.0.0
608
     *
609
     * @return array
610
     */
611
    public function loadCache()
612
    {
613
        $cache = new Cache('file');
614
        if ($cache->has('router')) {
615
            return $cache->get('router');
616
        }
617
    }
618
619
    /**
620
     * Cache the roouter.
621
     *
622
     * @since 1.0.0
623
     *
624
     * @return void
625
     */
626
    public function cacheRouters()
627
    {
628
        if (__config('app.router_cache') === true) {
0 ignored issues
show
introduced by
The condition __config('app.router_cache') === true is always false.
Loading history...
629
            $cache = new Cache('file');
630
            if (!$cache->has('router')) {
631
                $routers = $this->getRoutes();
632
                $cache->set('router', $routers, __config('app.router_cache_regenerate'));
633
            }
634
        }
635
    }
636
}
637