Completed
Push — master ( 546896...e1a197 )
by Anderson
02:36 queued 33s
created

Route::group()   C

Complexity

Conditions 12
Paths 64

Size

Total Lines 50
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 25
c 1
b 0
f 0
nc 64
nop 2
dl 0
loc 50
rs 5.3904

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * All improved routes parsed
28
     *
29
     * @var static array $routes Array of routes with meta data
30
     *
31
     * @access protected
32
     */
33
    protected static $routes = array();
34
35
36
    /**
37
     * CodeIgniter 'default_controller' index of the $route variable in config/routes.php
38
     *
39
     * @var static $defaultController
40
     *
41
     * @access protected
42
     */
43
    protected static $defaultController;
44
45
46
    /**
47
     * CodeIgniter '404_override' index of the $route variable in config/routes.php
48
     *
49
     * @var static $_404page
50
     *
51
     * @access protected
52
     */
53
    protected static $_404page = NULL;
54
55
    /**
56
     * CodeIgniter 'translate_uri_dashes' index of the $route variable in config/routes.php
57
     *
58
     * @var static $translateDashes
59
     *
60
     * @access protected
61
     */
62
    protected static $translateDashes = FALSE;
63
64
    /**
65
     * Array of hidden routes, it will parsed as an route with a show_404() callback into a clousure
66
     *
67
     * @var static $hiddenRoutes
68
     *
69
     * @access protected
70
     */
71
    protected static $hiddenRoutes = array();
72
73
    /**
74
     * (For route groups only) makes the 'hideOriginal' attribute global for the current group
75
     *
76
     * @var static $hideOriginals
77
     *
78
     * @access protected
79
     */
80
    protected static $hideOriginals = [];
81
82
    /**
83
     * (For route groups only) makes the 'prefix' attribute global for the current group
84
     *
85
     * @var static $prefix
86
     *
87
     * @access protected
88
     */
89
    protected static $prefix = [];
90
91
    /**
92
     * (For route groups only) makes the 'namespace' attribute global for the current group
93
     *
94
     * @var static $namespace
95
     *
96
     * @access protected
97
     */
98
    protected static $namespace = [];
99
100
    /**
101
     * Array with group middleware. It will be used with the Middleware class as a global route filter
102
     *
103
     * @var static $groupMiddleware
104
     *
105
     * @access protected
106
     */
107
    protected static $groupMiddleware = array();
108
109
    /**
110
     * Generic method to add a improved route
111
     *
112
     * @param  mixed $verb String or array of string of valid HTTP Verbs that will be accepted in this route
113
     * @param  mixed $url String or array of strings that will trigger this route
0 ignored issues
show
Bug introduced by
There is no parameter named $url. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
114
     * @param  array $attr Associative array of route attributes
115
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
116
     *
117
     * @return void
118
     *
119
     * @access public
120
     * @static
121
     */
122
    public static function add($verb, $path, $attr, $hideOriginal = TRUE, $return = FALSE)
123
    {
124
        if(!is_array($attr))
125
        {
126
            show_error('You must specify the route attributes as an array',500,'Route error: bad attributes');
127
        }
128
129
        if(!isset($attr['uses']))
130
        {
131
            show_error('Route requires a \'controller@method\' to be pointed and it\'s not defined in the route attributes', 500, 'Route error: missing controller');
132
        }
133
134
        if(!preg_match('/^([a-zA-Z1-9-_]+)@([a-zA-Z1-9-_]+)$/', $attr['uses'], $parsedController) !== FALSE)
135
        {
136
            show_error('Route controller must be in format controller@method', 500, 'Route error: bad controller format');
137
        }
138
139
140
        $controller = $parsedController[1];
141
        $method     = $parsedController[2];
142
143
        if(!is_string($path))
144
            show_error('Route path must be a string ', 500, 'Route error: bad route path');
145
146
        if(!is_string($verb))
147
            show_error('Route HTTP Verb must be a string', 500, 'Route error: bad verb type');
148
149
        $verb = strtoupper($verb);
150
151
        if(!in_array($verb, self::$http_verbs,TRUE))
152
        {
153
            $errorMsg = 'Verb "'.$verb.'" is not a valid HTTP Verb, allowed verbs:<ul>';
154
            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...
155
            {
156
                $errorMsg .= '<li>'.$validVerb.'</li>';
157
            }
158
            $errorMsg .= '</ul>';
159
            show_error($errorMsg,500,'Route error: unknow method');
160
        }
161
162
        $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...
163
164
        $path = trim($path,'/');
165
166
        $route['path']       = $path;
167
        $route['controller'] = $controller;
168
        $route['method']     = $method;
169
170
        if(isset($attr['as']))
171
        {
172
            $route['name'] = $attr['as'];
173
        }
174
        else
175
        {
176
            $route['name'] = NULL;
177
        }
178
179
        // Setting up the prefix
180
181
        $route['prefix'] = NULL;
182
        $group_prefix = end(self::$prefix);
183
184
        if($group_prefix)
185
            $route['prefix'] = $group_prefix.'/';
186
187
        if(isset($attr['prefix']))
188
            $route['prefix'] .= $attr['prefix'];
189
190
        // Setting up the namespace
191
192
        $route['namespace'] = NULL;
193
        $group_namespace = end(self::$namespace);
194
195
        if(!is_null($group_namespace))
196
            $route['namespace'] = $group_namespace.'/';
197
        if(isset($attr['namespace']))
198
            $route['namespace'] .= $attr['namespace'];
199
200
        $route['prefix']    = trim($route['prefix'], '/');
201
        $route['namespace'] = trim($route['namespace'],'/');
202
203
        if(empty($route['prefix']))
204
            $route['prefix'] = NULL;
205
206
        if(empty($route['namespace']))
207
            $route['namespace'] = NULL;
208
209
        $route['middleware'] = array();
210
211
        if(isset($attr['middleware']))
212
        {
213
            if(is_array($attr['middleware']))
214
            {
215
                foreach($attr['middleware'] as $middleware)
216
                    $route['middleware'][] = $middleware; # Group
217
            }
218
            elseif( is_string($attr['middleware']))
219
            {
220
                $route['middleware'][] = $attr['middleware']; # Group
221
            }
222
            else
223
            {
224
                show_error('Route middleware must be a string or an array',500,'Route error: bad middleware format');
225
            }
226
        }
227
228
        $compiledRoute = self::compileRoute((object) $route);
229
230
        $route['compiled'] =
231
            [
232
                $compiledRoute->path => $compiledRoute->route
233
            ];
234
235
        $groupHideOriginals = end(self::$hideOriginals);
236
237
        if($hideOriginal || $groupHideOriginals || ($compiledRoute->path != '' && $compiledRoute->path != '/' ) )
238
        {
239
            $hiddenRoutePath      = $controller.'/'.$method;
240
            $hiddenRouteNamespace = '';
241
242
            if(!is_null($route['namespace']))
243
            {
244
                $hiddenRouteNamespace = $route['namespace'].'/';
245
            }
246
247
            $hiddenRoutePath = $hiddenRouteNamespace.$hiddenRoutePath;
248
249
            if($method == 'index')
250
            {
251
                self::$hiddenRoutes[] = [ $hiddenRouteNamespace.$controller  => function(){ show_404(); }];
252
            }
253
254
            self::$hiddenRoutes[] = [$hiddenRoutePath => function(){ show_404(); }];
255
        }
256
257
        if(!$return)
258
        {
259
            self::$routes[] = (object) $route;
260
        }
261
        else
262
        {
263
            return (object) $route;
264
        }
265
    }
266
267
    /**
268
     * Adds a GET route, alias of Route::add('GET',$url,$attr,$hideOriginal)
269
     *
270
     * @param  mixed $url String or array of strings that will trigger this route
271
     * @param  array $attr Associative array of route attributes
272
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
273
     *
274
     * @return void
275
     *
276
     * @access public
277
     * @static
278
     */
279
    public static function get($url, $attr, $hideOriginal = TRUE)
280
    {
281
        self::add('GET', $url,$attr, $hideOriginal);
282
    }
283
284
    /**
285
     * Adds a POST route, alias of Route::add('POST',$url,$attr,$hideOriginal)
286
     *
287
     * @param  mixed $url String or array of strings that will trigger this route
288
     * @param  array $attr Associative array of route attributes
289
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
290
     *
291
     * @return void
292
     *
293
     * @access public
294
     * @static
295
     */
296
    public static function post($url, $attr, $hideOriginal = TRUE)
297
    {
298
        self::add('POST', $url,$attr, $hideOriginal);
299
    }
300
301
    /**
302
     * Adds a PUT route, alias of Route::add('PUT',$url,$attr,$hideOriginal)
303
     *
304
     * @param  mixed $url String or array of strings that will trigger this route
305
     * @param  array $attr Associative array of route attributes
306
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
307
     *
308
     * @return void
309
     *
310
     * @access public
311
     * @static
312
     */
313
    public static function put($url, $attr, $hideOriginal = TRUE)
314
    {
315
        self::add('PUT', $url,$attr, $hideOriginal);
316
    }
317
318
    /**
319
     * Adds a PATCH route, alias of Route::add('PATCH',$url,$attr,$hideOriginal)
320
     *
321
     * @param  mixed $url String or array of strings that will trigger this route
322
     * @param  array $attr Associative array of route attributes
323
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
324
     *
325
     * @return void
326
     *
327
     * @access public
328
     * @static
329
     */
330
    public static function patch($url, $attr, $hideOriginal = TRUE)
331
    {
332
        self::add('PATCH', $url,$attr, $hideOriginal);
333
    }
334
335
    /**
336
     * Adds a DELETE route, alias of Route::add('DELETE',$url,$attr,$hideOriginal)
337
     *
338
     * @param  mixed $url String or array of strings that will trigger this route
339
     * @param  array $attr Associative array of route attributes
340
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
341
     *
342
     * @return void
343
     *
344
     * @access public
345
     * @static
346
     */
347
    public static function delete($url, $attr, $hideOriginal = TRUE)
348
    {
349
        self::add('DELETE', $url,$attr, $hideOriginal);
350
    }
351
352
    /**
353
     * Adds a route with ALL accepted verbs on Route::$http_verbs
354
     *
355
     * @param  mixed $url String or array of strings that will trigger this route
356
     * @param  array $attr Associative array of route attributes
357
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
358
     *
359
     * @return void
360
     *
361
     * @access public
362
     * @static
363
     */
364
    public static function any($url, $attr, $hideOriginal = TRUE)
365
    {
366
        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...
367
        {
368
            $verb = strtolower($verb);
369
            self::add($verb, $url, $attr, $hideOriginal);
370
        }
371
    }
372
373
    /**
374
     * Adds a list of routes with the verbs contained in $verbs, alias of Route::add($verbs,$url,$attr,$hideOriginal)
375
     *
376
     * @param  mixed $verb String or array of string of valid HTTP Verbs that will be accepted in this route
0 ignored issues
show
Bug introduced by
There is no parameter named $verb. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
377
     * @param  mixed $url String or array of strings that will trigger this route
378
     * @param  array $attr Associative array of route attributes
379
     * @param  bool $hideOriginal (Optional) map the original $url as a route with a show_404() callback inside
380
     *
381
     * @return void
382
     *
383
     * @access public
384
     * @static
385
     */
386
    public static function matches($verbs, $url, $attr, $hideOriginal = FALSE)
387
    {
388
        if(!is_array($verbs))
389
            show_error('Route::matches() first argument must be an array of valid HTTP Verbs', 500, 'Route error: bad Route::matches() verb list');
390
391
        foreach($verbs as $verb)
392
        {
393
            self::add($verb, $url, $attr, $hideOriginal);
394
        }
395
    }
396
397
    /**
398
     * Adds a RESTFul route wich contains methods for create, read, update, view an specific resource
399
     *
400
     * This is a shorthand of creating
401
     *      Route::get('{{url}}',['uses' => '{controller}@index', 'as' => '{controller}.index']);
402
     *      Route::get('{{url}}/create',['uses' => '{controller}@create', 'as' => '{controller}.create']);
403
     *      Route::post('{{url}}',['uses' => '{controller}@store', 'as' => '{controller}.store']);
404
     *      Route::get('{{url}}/{slug}',['uses' => '{controller}@show', 'as' => '{controller}.show']);
405
     *      Route::get('{{url}}/edit',['uses' => '{controller}@edit', 'as' => '{controller}.edit']);
406
     *      Route::matches(['PUT','PATCH'],'{{url}}/{slug}',['uses' => '{controller}@update', 'as' => '{controller}.update']);
407
     *      Route::delete('{{url}}/{slug}',['uses' => '{controller}@delete', 'as' => '{controller}.delete']);
408
     *
409
     * PLEASE NOTE: This is NOT a crud generator, just a bundle of predefined routes.
410
     *
411
     * @param  string $url String or array of strings that will trigger this route
0 ignored issues
show
Bug introduced by
There is no parameter named $url. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
412
     * @param  string $controller Controller name (only controller name)
413
     * @param  array $attr Associative array of route attributes
414
     *
415
     * @return void
416
     *
417
     * @access public
418
     * @static
419
     */
420
    public static function resource($name, $controller, $attr = NULL)
421
    {
422
        $base_attr = array();
423
424
        $hideOriginal = FALSE;
425
426
        if(isset($attr['namespace']))
427
            $base_attr['namespace']  = $attr['namespace'];
428
429
        if(isset($attr['middleware']))
430
            $base_attr['middleware'] = $attr['middleware'];
431
432
        if(isset($attr['hideOriginal']))
433
            $hideOriginal = (bool) $attr['hideOriginal'];
434
435
        $base_attr['prefix'] = strtolower($name);
436
437
        if(isset($attr['prefix']))
438
            $base_attr['prefix']  = $attr['prefix'];
439
440
        $only = array();
441
442
        $controller = strtolower($controller);
443
444
        if(isset($attr['only']) && (is_array($attr['only']) || is_string($attr['only'])))
445
        {
446
            if(is_array($attr['only']))
447
            {
448
                $only  = $attr['only'];
449
            }
450
            else
451
            {
452
                $only[] = $attr['only'];
453
            }
454
        }
455
456 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...
457
        {
458
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@index',   'as' => $name.'.index']);
459
            self::get('/', $route_attr, $hideOriginal);
460
        }
461
462 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...
463
        {
464
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@create', 'as' => $name.'.create']);
465
            self::get('create', $route_attr, $hideOriginal);
466
        }
467
468 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...
469
        {
470
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@store', 'as' => $name.'.store']);
471
            self::post('/', $route_attr, $hideOriginal);
472
        }
473
474 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...
475
        {
476
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@show', 'as' => $name.'.show']);
477
            self::get('{slug}', $route_attr, $hideOriginal);
478
        }
479
480 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...
481
        {
482
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@edit', 'as' => $name.'.edit']);
483
            self::get('{slug}/edit', $route_attr, $hideOriginal);
484
        }
485
486 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...
487
        {
488
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@update', 'as' => $name.'.update']);
489
            self::matches(['PUT', 'PATCH'], '{slug}/update', $route_attr, $hideOriginal);
490
        }
491
492 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...
493
        {
494
            $route_attr = array_merge($base_attr, ['uses' => $controller.'@destroy', 'as' => $name.'.destroy']);
495
            self::delete('{slug}', $route_attr, $hideOriginal);
496
        }
497
    }
498
499
    /**
500
     * Compiles an improved route to a valid CodeIgniter route
501
     *
502
     * @param  array $route an improved route
503
     *
504
     * @return array
505
     *
506
     * @access public
507
     * @static
508
     */
509
    public static function compileRoute($route)
510
    {
511
        $prefix    = NULL;
512
        $namespace = NULL;
513
514
        if(!is_null($route->prefix))
515
        {
516
            $prefix = $route->prefix;
517
        }
518
519
        if(!is_null($route->namespace))
520
        {
521
            $namespace = $route->namespace;
522
        }
523
524
        $path = $route->path;
525
526
        if(!is_null($prefix))
527
            $path = $prefix.'/'.$path;
528
529
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
530
        if(substr($path, 0, 1) == "/" && strlen($path) > 1)
531
            $path = substr($path,1);
532
        */
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
542
        $replaces =
543
            [
544
                '{\((.*)\):[a-zA-Z0-9-_]*(\?}|})' => '($1)',   # Custom regular expression
545
                '{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...
546
                '{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...
547
                '{[a-zA-Z0-9-_]*(\?}|})'          => '(:any)', # Everything else
548
            ];
549
550
        $foundedArgs = [];
551
552
        foreach($replaces as $regex => $replace)
553
        {
554
            $matches = [];
555
            if(preg_match_all('/'.$regex.'/', $path, $matches ))
556
            {
557
                $foundedArgs = $matches[0];
558
            }
559
560
            $path = preg_replace('/'.$regex.'/', $replace, $path);
561
        }
562
563
        $argConstraint = FALSE;
564
565
        $args = [];
566
        $args['required'] = [];
567
        $args['optional'] = [];
568
569
        foreach($foundedArgs as $arg)
570
        {
571
            if(substr($arg,-2) == '?}')
572
            {
573
                $args['optional'][] = $arg;
574
                $argConstraint = TRUE;
575
            }
576
            else
577
            {
578
                if($argConstraint)
579
                    show_error('Optional route path argument not valid at this position', 500, 'Route error');
580
                $args['required'][] = $arg;
581
            }
582
        }
583
584
        if(count($foundedArgs) > 0)
585
        {
586
            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...
587
            {
588
                $controller .= '/$'.($i + 1);
589
            }
590
        }
591
592
        return (object) [
593
            'path'  => $path,
594
            'route' => $controller,
595
            'args'  => $args,
596
        ];
597
    }
598
599
    /**
600
     * Compile ALL improved routes into a valid CodeIgniter's associative array of routes
601
     *
602
     * @return array
603
     *
604
     * @access public
605
     * @static
606
     */
607
    public static function register()
608
    {
609
        $routes = array();
610
611
        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...
612
        {
613
            $compiled = self::compileRoute($route);
614
615
            if( !isset($routes[$compiled->path]) || $route->verb == 'GET' )
616
            {
617
                $routes[$compiled->path] = $compiled->route;
618
            }
619
620
            self::$routes[$index] = (object) $route;
621
        }
622
623
        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...
624
        {
625
            $path = key($route);
626
            $_404 = $route[$path];
627
628
            if(!isset($routes[$path]))
629
                $routes[$path] = $_404;
630
        }
631
632
        if(is_null(self::$defaultController))
633
            show_error('You must specify a home route: Route::home() as default controller!', 500, 'Route error: missing default controller');
634
635
        $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...
636
        $defaultController = $defaultController[key($defaultController)];
637
638
        $routes['default_controller'] = $defaultController;
639
640
        if(is_null(self::$_404page))
641
        {
642
            $routes['404_override'] = '';
643
        }
644
        else
645
        {
646
            $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...
647
        }
648
649
        $routes['translate_uri_dashes'] = self::$translateDashes;
650
651
        return $routes;
652
    }
653
654
    /**
655
     * Creates a group of routes with common attributes
656
     *
657
     * @param  array $attr set of global attributes
658
     * @param  callback $routes wich contains a set of Route methods
659
     *
660
     * @return void
661
     *
662
     * @access public
663
     * @static
664
     */
665
    public static function group($attr, $routes)
666
    {
667
        if(!is_array($attr))
668
            show_error('Group attribute must be a valid array');
669
670
        if(!isset($attr['prefix']))
671
            show_error('You must specify an prefix!');
672
673
        self::$prefix[] = $attr['prefix'];
674
675
        if(isset($attr['namespace']))
676
        {
677
            self::$namespace[] = $attr['namespace'];
678
        }
679
680
        if(isset($attr['hideOriginals']) && $attr['hideOriginals'] === TRUE)
681
        {
682
            self::$hideOriginals[] = TRUE;
683
        }
684
        else
685
        {
686
            self::$hideOriginals[] = FALSE;
687
        }
688
689
        if(isset($attr['middleware']))
690
        {
691
            if(is_array($attr['middleware']) || is_string($attr['middleware']))
692
            {
693
                if(is_array($attr['middleware']) && !empty($attr['middleware']))
694
                {
695
                    foreach($attr['middleware'] as $middleware)
696
                        self::$groupMiddleware[] = [ $attr['prefix'] => $middleware ];
697
                }
698
                else
699
                {
700
                    self::$groupMiddleware[] = [ $attr['prefix'] => $attr['middleware'] ];
701
                }
702
            }
703
            else
704
            {
705
                show_error('Group middleware not valid');
706
            }
707
        }
708
709
        $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...
710
711
        array_pop(self::$prefix);
712
        array_pop(self::$namespace);
713
        array_pop(self::$hideOriginals);
714
    }
715
716
    /**
717
     * Creates the 'default_controller' key in CodeIgniter's route array
718
     *
719
     * @param  string $route controller/method name
0 ignored issues
show
Bug introduced by
There is no parameter named $route. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
720
     * @param  string $alias (Optional) alias of the default controller
0 ignored issues
show
Bug introduced by
There is no parameter named $alias. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
721
     *
722
     * Due a CodeIgniter limitations, this route MAY NOT be a directory.
723
     *
724
     * @return void
725
     *
726
     * @access public
727
     * @static
728
     */
729
    public static function home($controller, $as = 'home', $attr = NULL)
730
    {
731
        $routeAttr =
732
            [
733
                'uses' => $controller,
734
                'as'   => $as
735
            ];
736
737
        if(!is_null($attr) && !is_array($attr))
738
            show_error('Default controller attributes must be an array',500,'Route error: bad attribute type');
739
740
        if(!is_null($attr))
741
            $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...
742
743
        if(isset($attr['prefix']))
744
            show_error('Default controller may not have a prefix!',500,'Route error: prefix not allowed');
745
746
        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...
747
    }
748
749
    /**
750
     * Get all the improved routes defined
751
     *
752
     * @return array List of all defined routes
753
     *
754
     * @access public
755
     * @static
756
     */
757
    public static function getRoutes($verb = NULL)
758
    {
759
        if(is_null($verb))
760
        {
761
            return self::$routes;
762
        }
763
        else
764
        {
765
            $routes = [];
766
            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...
767
            {
768
                if($route->verb == $verb)
769
                    $routes[] = $route;
770
            }
771
            return $routes;
772
        }
773
    }
774
775
    /**
776
     * Get all hidden routes
777
     *
778
     * @return array
779
     *
780
     * @access public
781
     * @static
782
     */
783
    public static function getHiddenRoutes()
784
    {
785
        return self::$hiddenRoutes;
786
    }
787
788
    /**
789
     * Get all middleware defined by route groups.
790
     *
791
     * This middleware actually works as uri filter since they will not check the route,
792
     * just check if the current uri string matches the prefix of the route group.
793
     *
794
     * @return array
795
     *
796
     * @access public
797
     * @static
798
     */
799
    public static function getGroupMiddleware()
800
    {
801
        return self::$groupMiddleware;
802
    }
803
804
    /**
805
     * Retrieve a route wich is called $search (if exists)
806
     *
807
     * @param  string $search The route name to search
808
     * @param  $args (Optional) The route arguments that will be parsed
809
     *
810
     * @return mixed Founded route in case of success, and error in case of no matches.
811
     *
812
     * @access public
813
     * @static
814
     */
815
    public static function getRouteByName($search)
816
    {
817
        $founded = NULL;
818
819
        $args = func_get_args();
820
        unset($args[0]);
821
822
        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...
823
        {
824
            if($route->name == $search)
825
            {
826
                $founded = $route;
827
            }
828
        }
829
830
        if(!is_null($founded))
831
        {
832
            $routeArgs        = self::compileRoute($founded)->args;
833
            $routeArgCount    = count($routeArgs['required']) + count($routeArgs['optional']);
834
            $routeReqArgCount = count($routeArgs['required']);
835
836
            if(count($args) < $routeReqArgCount)
837
            {
838
                $missingArgs = $routeReqArgCount - count($args);
839
                throw new \Exception('Missing '.$missingArgs.' required argument'.($missingArgs != 1 ? 's' : '').' for route "'.$founded->name.'"');
840
            }
841
            if(count($args) > $routeArgCount)
842
            {
843
                throw new \Exception('The route "'.$founded->name.'" expects maximum '.$routeArgCount.' argument'.($routeArgCount != 1 ? 's' : '').', '.count($args).' provided');
844
            }
845
846
            $path = self::compileRoute($founded)->path;
847
848
            foreach($args as $replacement)
849
            {
850
                $path = preg_replace('/\((.*?)\)/', $replacement, $path, 1);
851
            }
852
853
            $argsLeft =  $routeArgCount - count($args);
854
855
            for($i = $argsLeft; $i >= 0; $i--)
856
            {
857
                $path = preg_replace('/\((.*?)\)/', '', $path, 1);
858
            }
859
860
            return base_url(trim($path,'/'));
861
        }
862
863
        throw new \Exception('The route "'.$search.'" is not defined');
864
    }
865
866
    /**
867
     *  Heuristic testing of current uri_string in compiled routes
868
     *
869
     *  This is the 'reverse' process of the improved routing, it'll take the current
870
     *  uri string and attempts to find a CodeIgniter route that matches with his pattern
871
     *
872
     * @param  string $search
0 ignored issues
show
Bug introduced by
There is no parameter named $search. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
873
     *
874
     * @return mixed
875
     */
876
    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...
877
    {
878
        if(is_null($requestMethod))
879
            $requestMethod = $_SERVER['REQUEST_METHOD'];
880
881
        $routes = self::getRoutes($requestMethod);
882
883
        if(empty($routes))
884
            return FALSE;
885
886
        $founded = FALSE;
0 ignored issues
show
Unused Code introduced by
$founded 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...
887
        $matches = array();
0 ignored issues
show
Unused Code introduced by
$matches 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...
888
889
        $path = trim($path);
890
891
        if($path == '')
892
            return self::$defaultController;
893
894
        $wildcards =
895
            [
896
                '/\(:any\)/',
897
                '/\(:num\)/',
898
                '/\((.*?)\)/',
899
            ];
900
901
        $replaces =
902
            [
903
                '[^/]+',
904
                '[0-9]+',
905
                '(.*)'
906
            ];
907
908
909
        foreach( ['exact' , 'regex'] as $mode)
910
        {
911
            foreach( [ $path, $path.'/index' ] as $findPath )
912
            {
913
                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...
914
                {
915
                    $compiledPath = key($route->compiled);
916
917
                    if($mode == 'exact')
918
                    {
919
                        if($findPath == $compiledPath)
920
                            return $route;
921
                    }
922
                    else
923
                    {
924
                        $e_findPath     = explode('/', $findPath);
925
                        $e_compiledPath = explode('/', $compiledPath);
926
927
                        if( count($e_findPath) == count($e_compiledPath))
928
                        {
929
                            $valid = TRUE;
930
                            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...
931
                            {
932
                                $reg = preg_replace($wildcards, $replaces, $e_compiledPath[$i]);
933
934
                                $valid = (bool) preg_match('#^'.$reg.'$#', $e_findPath[$i]);
935
                            }
936
                            if($valid)
937
                                return $route;
938
                        }
939
                    }
940
                }
941
            }
942
        }
943
944
        return FALSE;
945
    }
946
947
    /**
948
     * Returns an array with the valid HTTP Verbs used in routes
949
     *
950
     * @return array
951
     *
952
     * @access public
953
     * @static
954
     */
955
    public static function getHTTPVerbs()
956
    {
957
        return self::$http_verbs;
958
    }
959
960
    /**
961
     * Set the 404 error controller ($route['404_override'])
962
     *
963
     * @param  string  $controller
964
     * @param  string  $namespace (Optional)
0 ignored issues
show
Bug introduced by
There is no parameter named $namespace. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
965
     *
966
     * @return void
967
     *
968
     * @access public
969
     * @static
970
     */
971
    public static function set404($controller, $path = '404')
972
    {
973
        self::$_404page = (object)
974
        [
975
            'controller' => $controller,
976
            'path'       => $path
977
        ];
978
    }
979
980
    /**
981
     * Get the 404 route
982
     *
983
     * @return array $_404page
984
     *
985
     * @return object | null
986
     *
987
     * @access public
988
     * @static
989
     */
990
    public static function get404()
991
    {
992
        return self::$_404page;
993
    }
994
995
    /**
996
     * Set the 'translate_uri_dashes' value ($route['translate_uri_dashes'])
997
     *
998
     * @param  $value
999
     *
1000
     * @return void
1001
     *
1002
     * @access public
1003
     * @static
1004
     */
1005
    public static function setTrasnlateUriDashes($value)
1006
    {
1007
        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...
1008
    }
1009
}