Completed
Push — master ( 19a002...9e523e )
by Anderson
02:02
created

Route::home()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 2
b 0
f 0
nc 8
nop 3
dl 0
loc 19
rs 8.8571
1
<?php
2
3
/**
4
 * Route class
5
 *
6
 * @author    Anderson Salas <[email protected]>
7
 * @copyright 2017
8
 * @license   GNU-3.0
9
 *
10
 */
11
12
namespace Luthier\Core;
13
14
class Route
15
{
16
17
    /**
18
     * Supported HTTP Verbs for this class
19
     *
20
     * @var static array $http_verbs Array of supported HTTP Verbs
21
     *
22
     * @access protected
23
     */
24
    protected static $http_verbs = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'TRACE', 'CONNECT', 'HEAD'];
25
26
27
    /**
28
     * All improved routes parsed
29
     *
30
     * @var static array $routes Array of routes with meta data
31
     *
32
     * @access protected
33
     */
34
    protected static $routes = array();
35
36
37
    /**
38
     * CodeIgniter 'default_controller' index of the $route variable in config/routes.php
39
     *
40
     * @var static $defaultController
41
     *
42
     * @access protected
43
     */
44
    protected static $defaultController;
45
46
47
    /**
48
     * CodeIgniter '404_override' index of the $route variable in config/routes.php
49
     *
50
     * @var static $_404page
51
     *
52
     * @access protected
53
     */
54
    protected static $_404page = NULL;
55
56
57
    /**
58
     * CodeIgniter 'translate_uri_dashes' index of the $route variable in config/routes.php
59
     *
60
     * @var static $translateDashes
61
     *
62
     * @access protected
63
     */
64
    protected static $translateDashes = FALSE;
65
66
67
    /**
68
     * Array of hidden routes, it will parsed as an route with a show_404() callback into a clousure
69
     *
70
     * @var static $hiddenRoutes
71
     *
72
     * @access protected
73
     */
74
    protected static $hiddenRoutes = array();
75
76
77
    /**
78
     * Route group '$hideOriginal' directive
79
     *
80
     * @var static $hideOriginals
81
     *
82
     * @access protected
83
     */
84
    protected static $hideOriginals = [];
85
86
87
    /**
88
     * Route group prefix
89
     *
90
     * @var static $prefix
91
     *
92
     * @access protected
93
     */
94
    protected static $prefix = [];
95
96
97
    /**
98
     * Route group namespace
99
     *
100
     * @var static $namespace
101
     *
102
     * @access protected
103
     */
104
    protected static $namespace = [];
105
106
107
    /**
108
     * Route group middleware
109
     *
110
     * @var static $middleware [add description]
111
     *
112
     * @access protected
113
     */
114
    protected static $middleware = [];
115
116
117
    /**
118
     * Generic method to add a improved route
119
     *
120
     * @param  mixed $verb String or array of string of valid HTTP Verbs that will be accepted in this route
121
     * @param  array $attr Associative array of route attributes
122
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
123
     *
124
     * @return void
125
     *
126
     * @access public
127
     * @static
128
     */
129
    public static function add($verb, $path, $attr, $hideOriginal = TRUE, $return = FALSE)
130
    {
131
        if (!is_array($attr))
132
        {
133
            show_error('You must specify the route attributes as an array', 500, 'Route error: bad attributes');
134
        }
135
136
        if (!isset($attr['uses']))
137
        {
138
            show_error('Route requires a \'controller@method\' to be pointed and it\'s not defined in the route attributes', 500, 'Route error: missing controller');
139
        }
140
141
        if (!preg_match('/^([a-zA-Z1-9-_]+)@([a-zA-Z1-9-_]+)$/', $attr['uses'], $parsedController) !== FALSE)
142
        {
143
            show_error('Route controller must be in format controller@method', 500, 'Route error: bad controller format');
144
        }
145
146
        $controller = $parsedController[1];
147
        $method     = $parsedController[2];
148
149
        if (!is_string($path))
150
            show_error('Route path must be a string ', 500, 'Route error: bad route path');
151
152
        if (!is_string($verb))
153
            show_error('Route HTTP Verb must be a string', 500, 'Route error: bad verb type');
154
155
        $verb = strtoupper($verb);
156
157
        if (!in_array($verb, self::$http_verbs, TRUE))
158
        {
159
            $errorMsg = 'Verb "'.$verb.'" is not a valid HTTP Verb, allowed verbs:<ul>';
160
            foreach (self::$http_verbs as $validVerb)
0 ignored issues
show
Bug introduced by
The expression self::$http_verbs of type object<Luthier\Core\Route> is not traversable.
Loading history...
161
            {
162
                $errorMsg .= '<li>'.$validVerb.'</li>';
163
            }
164
            $errorMsg .= '</ul>';
165
            show_error($errorMsg, 500, 'Route error: unknow method');
166
        }
167
168
        $route['verb'] = $verb;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$route was never initialized. Although not strictly required by PHP, it is generally a good practice to add $route = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
169
170
        $path = trim($path, '/');
171
172
        $route['path']       = $path;
173
        $route['controller'] = $controller;
174
        $route['method']     = $method;
175
176
        if (isset($attr['as']))
177
        {
178
            $route['name'] = $attr['as'];
179
        } else
180
        {
181
            $route['name'] = NULL;
182
        }
183
184
        // Setting up the prefix
185
186
        $route['prefix'] = NULL;
187
        $group_prefix = implode('/', self::$prefix);
188
189
        if ($group_prefix)
190
            $route['prefix'] = $group_prefix.'/';
191
192
        if (isset($attr['prefix']))
193
            $route['prefix'] .= $attr['prefix'];
194
195
        // Setting up the namespace
196
197
        $route['namespace'] = NULL;
198
        $group_namespace = implode('/', self::$namespace);
199
200
        if (!is_null($group_namespace))
201
            $route['namespace'] = $group_namespace.'/';
202
        if (isset($attr['namespace']))
203
            $route['namespace'] .= $attr['namespace'];
204
205
        $route['prefix']    = trim($route['prefix'], '/');
206
        $route['namespace'] = trim($route['namespace'], '/');
207
208
        if (empty($route['prefix']))
209
            $route['prefix'] = NULL;
210
211
        if (empty($route['namespace']))
212
            $route['namespace'] = NULL;
213
214
        // Route middleware
215
        $route['middleware'] = [];
216
        $route['middleware'] = array_merge($route['middleware'], self::$middleware);
217
218
        if (isset($attr['middleware']))
219
        {
220
            if (is_array($attr['middleware']))
221
            {
222
                foreach ($attr['middleware'] as $middleware)
223
                    $route['middleware'][] = $middleware; # Group
224
            }
225
            elseif (is_string($attr['middleware']))
226
            {
227
                $route['middleware'][] = $attr['middleware']; # Group
228
            }
229
            else
230
            {
231
                show_error('Route middleware must be a string or an array', 500, 'Route error: bad middleware format');
232
            }
233
        }
234
235
        $compiledRoute = self::compileRoute((object) $route);
236
237
        $route['compiled'] =
238
            [
239
                $compiledRoute->path => $compiledRoute->route
240
            ];
241
242
        $groupHideOriginals = end(self::$hideOriginals);
243
244
        if ($hideOriginal || $groupHideOriginals || ($compiledRoute->path != '' && $compiledRoute->path != '/'))
245
        {
246
            $hiddenRoutePath      = $controller.'/'.$method;
247
            $hiddenRouteNamespace = '';
248
249
            if (!is_null($route['namespace']))
250
            {
251
                $hiddenRouteNamespace = $route['namespace'].'/';
252
            }
253
254
            $hiddenRoutePath = $hiddenRouteNamespace.$hiddenRoutePath;
255
256
            if ($method == 'index')
257
            {
258
                self::$hiddenRoutes[] = [$hiddenRouteNamespace.$controller  => function() { self::trigger404(); }];
259
            }
260
261
            self::$hiddenRoutes[] = [$hiddenRoutePath => function() { self::trigger404(); }];
262
        }
263
264
        if (!$return)
265
        {
266
            self::$routes[] = (object) $route;
267
        } else
268
        {
269
            return (object) $route;
270
        }
271
    }
272
273
274
    /**
275
     * Adds a GET route, alias of Route::add('GET',$url,$attr,$hideOriginal)
276
     *
277
     * @param  string $url String or array of strings that will trigger this route
278
     * @param  array $attr Associative array of route attributes
279
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
280
     *
281
     * @return void
282
     *
283
     * @access public
284
     * @static
285
     */
286
    public static function get($url, $attr, $hideOriginal = TRUE)
287
    {
288
        self::add('GET', $url, $attr, $hideOriginal);
289
    }
290
291
292
    /**
293
     * Adds a POST route, alias of Route::add('POST',$url,$attr,$hideOriginal)
294
     *
295
     * @param  string $url String or array of strings that will trigger this route
296
     * @param  array $attr Associative array of route attributes
297
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
298
     *
299
     * @return void
300
     *
301
     * @access public
302
     * @static
303
     */
304
    public static function post($url, $attr, $hideOriginal = TRUE)
305
    {
306
        self::add('POST', $url, $attr, $hideOriginal);
307
    }
308
309
310
    /**
311
     * Adds a PUT route, alias of Route::add('PUT',$url,$attr,$hideOriginal)
312
     *
313
     * @param  mixed $url String or array of strings that will trigger this route
314
     * @param  array $attr Associative array of route attributes
315
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
316
     *
317
     * @return void
318
     *
319
     * @access public
320
     * @static
321
     */
322
    public static function put($url, $attr, $hideOriginal = TRUE)
323
    {
324
        self::add('PUT', $url, $attr, $hideOriginal);
325
    }
326
327
328
    /**
329
     * Adds a PATCH route, alias of Route::add('PATCH',$url,$attr,$hideOriginal)
330
     *
331
     * @param  mixed $url String or array of strings that will trigger this route
332
     * @param  array $attr Associative array of route attributes
333
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
334
     *
335
     * @return void
336
     *
337
     * @access public
338
     * @static
339
     */
340
    public static function patch($url, $attr, $hideOriginal = TRUE)
341
    {
342
        self::add('PATCH', $url, $attr, $hideOriginal);
343
    }
344
345
346
    /**
347
     * Adds a DELETE route, alias of Route::add('DELETE',$url,$attr,$hideOriginal)
348
     *
349
     * @param  string $url String or array of strings that will trigger this route
350
     * @param  array $attr Associative array of route attributes
351
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
352
     *
353
     * @return void
354
     *
355
     * @access public
356
     * @static
357
     */
358
    public static function delete($url, $attr, $hideOriginal = TRUE)
359
    {
360
        self::add('DELETE', $url, $attr, $hideOriginal);
361
    }
362
363
364
    /**
365
     * Adds a route with ALL accepted verbs on Route::$http_verbs
366
     *
367
     * @param  mixed $url String or array of strings that will trigger this route
368
     * @param  array $attr Associative array of route attributes
369
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
370
     *
371
     * @return void
372
     *
373
     * @access public
374
     * @static
375
     */
376
    public static function any($url, $attr, $hideOriginal = TRUE)
377
    {
378
        foreach (self::$http_verbs as $verb)
0 ignored issues
show
Bug introduced by
The expression self::$http_verbs of type object<Luthier\Core\Route> is not traversable.
Loading history...
379
        {
380
            $verb = strtolower($verb);
381
            self::add($verb, $url, $attr, $hideOriginal);
382
        }
383
    }
384
385
386
    /**
387
     * Adds a list of routes with the verbs contained in $verbs, alias of Route::add($verbs,$url,$attr,$hideOriginal)
388
     *
389
     * @param  string[] $verbs String or array of string of valid HTTP Verbs that will be accepted in this route
390
     * @param  string $url String or array of strings that will trigger this route
391
     * @param  array $attr Associative array of route attributes
392
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
393
     *
394
     * @return void
395
     *
396
     * @access public
397
     * @static
398
     */
399
    public static function matches($verbs, $url, $attr, $hideOriginal = FALSE)
400
    {
401
        if (!is_array($verbs))
402
            show_error('Route::matches() first argument must be an array of valid HTTP Verbs', 500, 'Route error: bad Route::matches() verb list');
403
404
        foreach ($verbs as $verb)
405
        {
406
            self::add($verb, $url, $attr, $hideOriginal);
407
        }
408
    }
409
410
411
    /**
412
     * Adds a RESTFul route wich contains methods for create, read, update, view an specific resource
413
     *
414
     *
415
     * @param  string $name
416
     * @param  string $controller
417
     * @param  array $attr
418
     *
419
     * @return void
420
     *
421
     * @access public
422
     * @static
423
     */
424
    public static function resource($name, $controller, $attr = NULL)
425
    {
426
        $base_attr = [];
427
428
        $hideOriginal = FALSE;
429
430
        if (isset($attr['namespace']))
431
            $base_attr['namespace']  = $attr['namespace'];
432
433
        if (isset($attr['middleware']))
434
            $base_attr['middleware'] = $attr['middleware'];
435
436
        if (isset($attr['hideOriginal']))
437
            $hideOriginal = (bool) $attr['hideOriginal'];
438
439
        $base_attr['prefix'] = strtolower($name);
440
441
        if (isset($attr['prefix']))
442
            $base_attr['prefix'] = $attr['prefix'];
443
444
        $only = [];
445
446
        $controller = strtolower($controller);
447
448
        if (isset($attr['only']) && (is_array($attr['only']) || is_string($attr['only'])))
449
        {
450
            if (is_array($attr['only']))
451
            {
452
                $only = $attr['only'];
453
            }
454
            else
455
            {
456
                $only[] = $attr['only'];
457
            }
458
        }
459
460 View Code Duplication
        if (empty($only) || in_array('index', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
461
        {
462
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@index', 'as' => $name.'.index']);
463
            self::get('/', $route_attr, $hideOriginal);
464
        }
465
466 View Code Duplication
        if (empty($only) || in_array('create', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
467
        {
468
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@create', 'as' => $name.'.create']);
469
            self::get('create', $route_attr, $hideOriginal);
470
        }
471
472 View Code Duplication
        if (empty($only) || in_array('store', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
473
        {
474
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@store', 'as' => $name.'.store']);
475
            self::post('/', $route_attr, $hideOriginal);
476
        }
477
478 View Code Duplication
        if (empty($only) || in_array('show', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
        {
480
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@show', 'as' => $name.'.show']);
481
            self::get('{slug}', $route_attr, $hideOriginal);
482
        }
483
484 View Code Duplication
        if (empty($only) || in_array('edit', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
485
        {
486
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@edit', 'as' => $name.'.edit']);
487
            self::get('{slug}/edit', $route_attr, $hideOriginal);
488
        }
489
490 View Code Duplication
        if (empty($only) || in_array('update', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
491
        {
492
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@update', 'as' => $name.'.update']);
493
            self::matches(['PUT', 'PATCH'], '{slug}/update', $route_attr, $hideOriginal);
494
        }
495
496 View Code Duplication
        if (empty($only) || in_array('destroy', $only))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
497
        {
498
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@destroy', 'as' => $name.'.destroy']);
499
            self::delete('{slug}', $route_attr, $hideOriginal);
500
        }
501
    }
502
503
504
    /**
505
     * Compiles an improved route to a valid CodeIgniter route
506
     *
507
     * @param  array $route an improved route
508
     *
509
     * @return array
510
     *
511
     * @access public
512
     * @static
513
     */
514
    public static function compileRoute($route)
515
    {
516
        $prefix    = NULL;
517
        $namespace = NULL;
518
519
        if (!is_null($route->prefix))
520
        {
521
            $prefix = $route->prefix;
522
        }
523
524
        if (!is_null($route->namespace))
525
        {
526
            $namespace = $route->namespace;
527
        }
528
529
        $path = $route->path;
530
531
        if (!is_null($prefix))
532
            $path = $prefix.'/'.$path;
533
534
        $controller = $route->controller.'/'.$route->method;
535
536
        if (!is_null($namespace))
537
            $controller = $namespace.'/'.$controller;
538
539
        $path       = trim($path, '/');
540
        $controller = trim($controller, '/');
541
        $baseController = $controller;
542
543
        $replaces =
544
            [
545
                '{\((.*)\):[a-zA-Z0-9-_]*(\?}|})' => '($1)', # Custom regular expression
546
                '{num:[a-zA-Z0-9-_]*(\?}|})'      => '(:num)', # (:num) route
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
547
                '{any:[a-zA-Z0-9-_]*(\?}|})'      => '(:any)', # (:any) route
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
548
                '{[a-zA-Z0-9-_]*(\?}|})'          => '(:any)', # Everything else
549
            ];
550
551
        $foundedArgs = [];
552
        $basePath    = '';
553
554
555
        foreach (explode('/', $path) as $path_segment)
556
        {
557
            if (!preg_match('/^\{(.*)\}$/', $path_segment))
558
            {
559
                $basePath .= $path_segment.'/';
560
            }
561
        }
562
563
        $basePath = trim($basePath, '/');
564
565
        foreach ($replaces as $regex => $replace)
566
        {
567
            $matches = [];
568
569
            //if(preg_match_all('/'.$regex.'/', $path, $matches ))
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
570
            if (preg_match_all('/\{(.*)\}/', $path, $matches))
571
            {
572
                //$foundedArgs = $matches[0];
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
573
                $foundedArgs = explode('/', $matches[0][0]);
574
                //var_dump($path, $foundedArgs);
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
575
            }
576
577
            $path = preg_replace('/'.$regex.'/', $replace, $path);
578
        }
579
580
        $argConstraint = FALSE;
581
582
        $args = [];
583
        $args['required'] = [];
584
        $args['optional'] = [];
585
586
        foreach ($foundedArgs as $arg)
587
        {
588
            if (substr($arg, -2) == '?}')
589
            {
590
                $args['optional'][] = $arg;
591
                $argConstraint = TRUE;
592
            }
593
            else
594
            {
595
                if ($argConstraint)
596
                    show_error('Optional route path argument not valid at this position', 500, 'Route error');
597
                $args['required'][] = $arg;
598
            }
599
        }
600
601
        if (count($foundedArgs) > 0)
602
        {
603
            for ($i = 0; $i < count($foundedArgs); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
604
            {
605
                $controller .= '/$'.($i + 1);
606
            }
607
        }
608
609
        return (object) [
610
            'path'      => $path,
611
            'route'     => $controller,
612
            'args'      => $args,
613
            'baseRoute' => $baseController,
614
            'basePath'  => $basePath
615
        ];
616
    }
617
618
619
    /**
620
     * Compile ALL improved routes into a valid CodeIgniter's associative array of routes
621
     *
622
     * @return array
623
     *
624
     * @access public
625
     * @static
626
     */
627
    public static function register()
628
    {
629
        $routes = array();
630
631
        foreach (self::$routes as $index => $route)
0 ignored issues
show
Bug introduced by
The expression self::$routes of type object<Luthier\Core\Route> is not traversable.
Loading history...
632
        {
633
            $compiled = self::compileRoute($route);
634
635
            $backtrackingPath  = '';
0 ignored issues
show
Unused Code introduced by
$backtrackingPath is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
636
            $backtrackingRoute = '';
0 ignored issues
show
Unused Code introduced by
$backtrackingRoute is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
637
638
            if (count($compiled->args['optional']) > 0)
639
            {
640
                $e_path  = explode('/', $compiled->path);
641
                $e_route = explode('/', $compiled->route);
0 ignored issues
show
Unused Code introduced by
$e_route is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
642
643
                $basePath = $compiled->basePath;
644
                $baseRoute = $compiled->baseRoute;
645
646
                $a = count(explode('/', $basePath));
647
648
                for ($r = 0; $r < count($compiled->args['required']); $r++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
649
                {
650
                    $basePath .= '/'.$e_path[$a + $r];
651
                    $baseRoute .= '/'.'$'.($r + 1);
652
                }
653
654
                $a = count(explode('/', $basePath));
655
                $b = ($r + 1);
656
657
                $backtracking = [];
658
659
                for ($o = 0; $o <= count($compiled->args['optional']); $o++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
660
                {
661
                    $backtrackingPath  = $basePath;
662
                    $backtrackingRoute = $baseRoute;
663
664
                    for ($c = 0; $c < $o; $c++)
665
                    {
666
                        $backtrackingPath .= '/'.$e_path[$a + $c - 1];
667
                        $backtrackingRoute .= '/'.'$'.($b + $c);
668
                    }
669
670
                    $backtracking[$o] = ['path' => $backtrackingPath, 'route' => $backtrackingRoute];
671
                }
672
673
                foreach ($backtracking as $b_route)
674
                {
675
                    $b_compiled   = self::compileRoute($route);
676
                    $b_args       = array_merge($b_compiled->args['required'], $b_compiled->args['optional']);
677
                    $b_route_path = $b_route['path'];
678
679
                    foreach ($b_args as $arg)
680
                    {
681
                        $b_route_path = preg_replace('/\((.*?)\)/', $arg, $b_route_path, 1);
682
                    }
683
684
                    self::add($route->verb, $b_route_path, ['uses' => $route->controller.'@'.$route->method]);
685
686
                    if (!isset($routes[$b_route['path']]) || $route->verb == 'GET')
687
                    {
688
                        $routes[$b_route['path']] = $b_route['route'];
689
                    }
690
                }
691
            }
692
693
            if (!isset($routes[$compiled->path]) || $route->verb == 'GET')
694
            {
695
                $routes[$compiled->path] = $compiled->route;
696
            }
697
698
            self::$routes[$index] = (object) $route;
699
        }
700
701
        foreach (self::$hiddenRoutes as $route)
0 ignored issues
show
Bug introduced by
The expression self::$hiddenRoutes of type object<Luthier\Core\Route> is not traversable.
Loading history...
702
        {
703
            $path = key($route);
704
            $_404 = $route[$path];
705
706
            if (!isset($routes[$path]))
707
                $routes[$path] = $_404;
708
        }
709
710
        if (is_null(self::$defaultController))
711
            show_error('You must specify a home route: Route::home() as default controller!', 500, 'Route error: missing default controller');
712
713
        $defaultController = self::$defaultController->compiled;
0 ignored issues
show
Bug introduced by
The property compiled does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
714
        $defaultController = $defaultController[key($defaultController)];
715
716
        $routes['default_controller'] = $defaultController;
717
718
        if (is_null(self::$_404page))
719
        {
720
            $routes['404_override'] = '';
721
        } else
722
        {
723
            $routes['404_override'] = self::$_404page->controller;
0 ignored issues
show
Bug introduced by
The property controller does not seem to exist. Did you mean defaultController?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
724
        }
725
726
        $routes['translate_uri_dashes'] = self::$translateDashes;
727
728
        return $routes;
729
    }
730
731
732
    /**
733
     * Creates a group of routes with common attributes
734
     *
735
     * @param  array $attr set of global attributes
736
     * @param  callback $routes wich contains a set of Route methods
737
     *
738
     * @return void
739
     *
740
     * @access public
741
     * @static
742
     */
743
    public static function group($attr, $routes)
744
    {
745
        if (!is_array($attr))
746
            show_error('Group attribute must be a valid array');
747
748
        if (!isset($attr['prefix']))
749
            show_error('You must specify an prefix!');
750
751
        self::$prefix[] = $attr['prefix'];
752
753
        if (isset($attr['namespace']))
754
        {
755
            self::$namespace[] = $attr['namespace'];
756
        }
757
758
        if (isset($attr['hideOriginals']) && $attr['hideOriginals'] === TRUE)
759
        {
760
            self::$hideOriginals[] = TRUE;
761
        } else
762
        {
763
            self::$hideOriginals[] = FALSE;
764
        }
765
766
        if (isset($attr['middleware']))
767
        {
768
            if (is_array($attr['middleware']) || is_string($attr['middleware']))
769
            {
770
                if (is_array($attr['middleware']) && !empty($attr['middleware']))
771
                {
772
                    foreach ($attr['middleware'] as $middleware)
773
                        self::$middleware[] = $middleware;
774
                }
775
                else
776
                {
777
                    self::$middleware[] = $attr['middleware'];
778
                }
779
            }
780
            else
781
            {
782
                show_error('Group middleware must be an array o a string', 500, 'Route error');
783
            }
784
        }
785
786
        $res = $routes->__invoke();
0 ignored issues
show
Bug introduced by
The method __invoke cannot be called on $routes (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Unused Code introduced by
$res is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
787
788
        array_pop(self::$prefix);
789
        array_pop(self::$namespace);
790
        array_pop(self::$middleware);
791
        array_pop(self::$hideOriginals);
792
    }
793
794
795
    /**
796
     * Creates the 'default_controller' key in CodeIgniter's route array
797
     *
798
     *
799
     * @return void
800
     *
801
     * @access public
802
     * @static
803
     */
804
    public static function home($controller, $as = 'home', $attr = NULL)
805
    {
806
        $routeAttr =
807
            [
808
                'uses' => $controller,
809
                'as'   => $as
810
            ];
811
812
        if (!is_null($attr) && !is_array($attr))
813
            show_error('Default controller attributes must be an array', 500, 'Route error: bad attribute type');
814
815
        if (!is_null($attr))
816
            $routeAttr = array_merge($routeAttr, $attr);
0 ignored issues
show
Unused Code introduced by
$routeAttr is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
817
818
        if (isset($attr['prefix']))
819
            show_error('Default controller may not have a prefix!', 500, 'Route error: prefix not allowed');
820
821
        self::$defaultController = self::$routes[] = self::add('GET', '/', ['uses' => $controller, 'as' => $as], TRUE, TRUE);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to self::$routes[] is correct as self::add('GET', '/', ar...s' => $as), TRUE, TRUE) (which targets Luthier\Core\Route::add()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

Loading history...
822
    }
823
824
825
    /**
826
     * Get all the improved routes defined
827
     *
828
     * @return array List of all defined routes
829
     *
830
     * @access public
831
     * @static
832
     */
833
    public static function getRoutes($verb = NULL)
834
    {
835
        if (is_null($verb))
836
        {
837
            return self::$routes;
838
        }
839
        else
840
        {
841
            $routes = [];
842
            foreach (self::$routes as $route)
0 ignored issues
show
Bug introduced by
The expression self::$routes of type object<Luthier\Core\Route> is not traversable.
Loading history...
843
            {
844
                if ($route->verb == $verb)
845
                    $routes[] = $route;
846
            }
847
            return $routes;
848
        }
849
    }
850
851
852
    /**
853
     * Get all hidden routes
854
     *
855
     * @return Route
856
     *
857
     * @access public
858
     * @static
859
     */
860
    public static function getHiddenRoutes()
861
    {
862
        return self::$hiddenRoutes;
863
    }
864
865
866
    /**
867
     * Retrieve a route wich is called $search (if exists)
868
     *
869
     * @param  string $search The route name to search
870
     * @param  $args (Optional) The route arguments that will be parsed
871
     *
872
     * @return mixed Founded route in case of success, and error in case of no matches.
873
     *
874
     * @access public
875
     * @static
876
     */
877
    public static function getRouteByName($search)
878
    {
879
        $founded = NULL;
880
881
        $args = func_get_args();
882
        unset($args[0]);
883
884
        foreach (self::$routes as $route)
0 ignored issues
show
Bug introduced by
The expression self::$routes of type object<Luthier\Core\Route> is not traversable.
Loading history...
885
        {
886
            if ($route->name == $search)
887
            {
888
                $founded = $route;
889
            }
890
        }
891
892
        if (!is_null($founded))
893
        {
894
            $routeArgs        = self::compileRoute($founded)->args;
895
            $routeArgCount    = count($routeArgs['required']) + count($routeArgs['optional']);
896
            $routeReqArgCount = count($routeArgs['required']);
897
898
            if (count($args) < $routeReqArgCount)
899
            {
900
                $missingArgs = $routeReqArgCount - count($args);
901
                throw new \Exception('Missing '.$missingArgs.' required argument'.($missingArgs != 1 ? 's' : '').' for route "'.$founded->name.'"');
902
            }
903
            if (count($args) > $routeArgCount)
904
            {
905
                throw new \Exception('The route "'.$founded->name.'" expects maximum '.$routeArgCount.' argument'.($routeArgCount != 1 ? 's' : '').', '.count($args).' provided');
906
            }
907
908
            $path = self::compileRoute($founded)->path;
909
910
            foreach ($args as $replacement)
911
            {
912
                $path = preg_replace('/\((.*?)\)/', $replacement, $path, 1);
913
            }
914
915
            $argsLeft = $routeArgCount - count($args);
916
917
            for ($i = $argsLeft; $i >= 0; $i--)
918
            {
919
                $path = preg_replace('/\((.*?)\)/', '', $path, 1);
920
            }
921
922
            return base_url(trim($path, '/'));
923
        }
924
925
        throw new \Exception('The route "'.$search.'" is not defined');
926
    }
927
928
929
    /**
930
     *  Heuristic testing of current uri_string in compiled routes
931
     *
932
     *  This is the 'reverse' process of the improved routing, it'll take the current
933
     *  uri string and attempts to find a CodeIgniter route that matches with his pattern
934
     *
935
     *
936
     * @param Middleware $path
937
     * @param string $requestMethod
938
     * @return mixed
939
     */
940
    public static function getRouteByPath($path, $requestMethod = NULL)
0 ignored issues
show
Coding Style introduced by
getRouteByPath uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
941
    {
942
        if (is_null($requestMethod))
943
            $requestMethod = $_SERVER['REQUEST_METHOD'];
944
945
        $routes = self::getRoutes($requestMethod);
946
947
        if (empty($routes))
948
            return FALSE;
949
950
        $path = trim($path);
951
952
        if ($path == '')
953
            return self::$defaultController;
954
955
        $wildcards =
956
            [
957
                '/\(:any\)/',
958
                '/\(:num\)/',
959
                '/\((.*?)\)/',
960
            ];
961
962
        $replaces =
963
            [
964
                '[^/]+',
965
                '[0-9]+',
966
                '(.*)'
967
            ];
968
969
        foreach (['exact', 'regex'] as $mode)
970
        {
971
            foreach ([$path, $path.'/index'] as $findPath)
972
            {
973
                foreach ($routes as $route)
0 ignored issues
show
Bug introduced by
The expression $routes of type object<Luthier\Core\Route>|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
974
                {
975
                    $compiledPath = key($route->compiled);
976
977
                    if ($mode == 'exact')
978
                    {
979
                        if ($findPath == $compiledPath)
980
                            return $route;
981
                    }
982
                    else
983
                    {
984
                        $e_findPath     = explode('/', $findPath);
985
                        $e_compiledPath = explode('/', $compiledPath);
986
987
                        if (count($e_findPath) == count($e_compiledPath))
988
                        {
989
                            $valid    = TRUE;
990
                            $skip_seg = [];
991
992
                            for ($i = 0; $i < count($e_findPath); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
993
                            {
994
                                $count = 0;
995
                                $reg   = preg_replace($wildcards, $replaces, $e_compiledPath[$i], -1, $count);
996
                                $valid = (bool) preg_match('#^'.$reg.'$#', $e_findPath[$i]);
997
998
                                if ($valid && $count > 0)
999
                                    $skip_seg[] = $i;
1000
                            }
1001
1002
                            if ($valid)
1003
                            {
1004
                                for ($i = 0; $i < count($e_findPath); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1005
                                {
1006
                                    if(in_array($i, $skip_seg))
1007
                                        continue;
1008
1009
                                    if ($valid)
1010
                                        $valid = $e_findPath[$i] == $e_compiledPath[$i];
1011
                                }
1012
                            }
1013
1014
                            if ($valid)
1015
                                return $route;
1016
                        }
1017
                    }
1018
                }
1019
            }
1020
        }
1021
1022
        return FALSE;
1023
    }
1024
1025
1026
    /**
1027
     * Parse improved route arguments by a provided path
1028
     *
1029
     * @param  object  $route
1030
     * @param  string  $path
1031
     *
1032
     * @return bool | object
1033
     *
1034
     * @access public
1035
     * @static
1036
     */
1037
    public static function getRouteArgs($route, $path)
1038
    {
1039
        $compiled = self::compileRoute($route);
0 ignored issues
show
Documentation introduced by
$route is of type object, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1040
1041
        $r_seg = explode('/', $compiled->path);
1042
        $p_seg = explode('/', $path);
1043
1044
        $args   = [];
1045
        $n_args = 1;
1046
1047
        for ($s = 0; $s < count($r_seg); $s++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1048
        {
1049
            if (!isset($p_seg[$s]))
1050
                continue;
1051
1052
            if ($r_seg[$s] != $p_seg[$s])
1053
            {
1054
                $args['$'.$n_args] = $p_seg[$s];
1055
                $n_args++;
1056
            }
1057
        }
1058
1059
        return $args;
1060
    }
1061
1062
1063
    /**
1064
     * Returns an array with the valid HTTP Verbs used in routes
1065
     *
1066
     * @return Route
1067
     *
1068
     * @access public
1069
     * @static
1070
     */
1071
    public static function getHTTPVerbs()
1072
    {
1073
        return self::$http_verbs;
1074
    }
1075
1076
1077
    /**
1078
     * Set the 404 error controller ($route['404_override'])
1079
     *
1080
     * @param  string  $controller
1081
     *
1082
     * @return void
1083
     *
1084
     * @access public
1085
     * @static
1086
     */
1087
    public static function set404($controller, $path = '404')
1088
    {
1089
        self::$_404page = (object)
1090
        [
1091
            'controller' => $controller,
1092
            'path'       => $path
1093
        ];
1094
    }
1095
1096
1097
    /**
1098
     * Get the 404 route
1099
     *
1100
     * @return Route $_404page
1101
     *
1102
     * @return Route | null
1103
     *
1104
     * @access public
1105
     * @static
1106
     */
1107
    public static function get404()
1108
    {
1109
        return self::$_404page;
1110
    }
1111
1112
1113
    /**
1114
     * Set the 'translate_uri_dashes' value ($route['translate_uri_dashes'])
1115
     *
1116
     * @param  $value
1117
     *
1118
     * @return void
1119
     *
1120
     * @access public
1121
     * @static
1122
     */
1123
    public static function setTrasnlateUriDashes($value)
1124
    {
1125
        self::$translateDashes = (bool) $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like (bool) $value of type boolean is incompatible with the declared type object<Luthier\Core\Route> of property $translateDashes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1126
    }
1127
1128
1129
    /**
1130
     * Attempts to trigger a nice 404 view (if a custom 404 controller is defined)
1131
     *
1132
     * @return void
1133
     *
1134
     * @access public
1135
     * @static
1136
     */
1137
    public static function trigger404()
1138
    {
1139
        if (!is_null(self::$_404page))
1140
        {
1141
            header('Location: '.config_item('base_url').self::$_404page->path);
0 ignored issues
show
Bug introduced by
The property path does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1142
            die;
0 ignored issues
show
Coding Style Compatibility introduced by
The method trigger404() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1143
        }
1144
1145
        show_404();
1146
    }
1147
}