GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Route   C
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 644
Duplicated Lines 3.26 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 21
loc 644
rs 5.365
c 0
b 0
f 0
wmc 70
lcom 1
cbo 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A map() 0 12 3
A any() 0 4 1
A setDefaultConstraint() 0 6 2
A registerConstraint() 0 18 3
A named() 0 8 2
A group() 0 14 1
A match() 0 8 2
A get() 3 6 3
A post() 3 6 3
A put() 3 6 3
A delete() 3 6 3
A head() 3 6 3
A patch() 3 6 3
A options() 3 6 3
B resources() 0 35 5
A area() 0 15 2
A getAreaName() 0 10 3
A environment() 0 10 2
A block() 0 12 4
A reset() 0 7 1
D create() 0 46 10
B checkSubdomains() 0 23 5
A determineCurrentSubdomain() 0 20 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Route often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Route, and based on these observations, apply Extract Interface, too.

1
<?php namespace Myth;
2
/**
3
 * Sprint
4
 *
5
 * A set of power tools to enhance the CodeIgniter framework and provide consistent workflow.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package     Sprint
26
 * @author      Lonnie Ezell
27
 * @copyright   Copyright 2014-2015, New Myth Media, LLC (http://newmythmedia.com)
28
 * @license     http://opensource.org/licenses/MIT  (MIT)
29
 * @link        http://sprintphp.com
30
 * @since       Version 1.0
31
 */
32
33
/**
34
 * Route class provides methods to be used within the routes config file
35
 * to enable a simpler syntax for some of the non-CI native methods.
36
 *
37
 * Thanks to Jamie Rumbelow and his wonderful Pigeon routing class for the
38
 * ideas for teh HTTP Verb-based routing in use here.
39
 *
40
 * @package Bonfire
41
 * @since   1.0
42
 */
43
class Route
44
{
45
46
    // Our routes, ripe for the picking.
47
    public $routes = array();
48
49
    // Holds key/value pairs of named routes
50
    public static $names = array();
51
52
    // Used for grouping routes together.
53
    public $group = null;
54
55
    // Holds the 'areas' of the site.
56
    public static $areas = array();
57
58
    // The default controller to use in case
59
    // 'default_controller' is not in the routes file.
60
    protected $default_home = 'home';
61
62
    // The default constraint to use in route building
63
    protected $default_constraint = 'any';
64
65
    protected $constraints = [
66
        'any'  => '(:any)',
67
        'num'  => '(:num)',
68
        'id'   => '(:num)',
69
        'name' => "([a-zA-Z']+)"
70
    ];
71
72
    protected $current_subdomain = null;
73
74
    //--------------------------------------------------------------------
75
76
    /**
77
     * Combines the routes that we've defined with the Route class with the
78
     * routes passed in. This is intended to be used  after all routes have been
79
     * defined to merge CI's default $route array with our routes.
80
     *
81
     * Example:
82
     *     $route['default_controller'] = 'home';
83
     *     Route::resource('posts');
84
     *     $route = Route::map($route);
85
     *
86
     * @param array $routes
87
     * @internal param array $route The array to merge
88
     * @return array         The merge route array.
89
     */
90
    public function map($routes = array())
91
    {
92
        $controller = isset($routes['default_controller']) ? $routes['default_controller'] : $this->default_home;
93
94
        $routes = array_merge($routes, $this->routes);
95
96
        foreach ($routes as $from => $to) {
97
            $routes[$from] = str_ireplace('{default_controller}', $controller, $to);
98
        }
99
100
        return $routes;
101
    }
102
103
    //--------------------------------------------------------------------
104
105
    /**
106
     * A single point to the basic routing. Can be used in place of CI's $route
107
     * array if desired. Used internally by many of the methods.
108
     *
109
     * Available options are currently:
110
     *      'as'        - remembers the route via a name that can be called outside of it.
111
     *      'offset'    - Offsets and parameters ($1, $2, etc) in routes by the specified amount.
112
     *                    Useful while doing versioning of API's, etc.
113
     *
114
     * Example:
115
     *      $route->any('news', 'posts/index');
116
     *
117
     * @param string $from
118
     * @param string $to
119
     * @param array  $options
120
     * @return void
121
     */
122
    public function any($from, $to, $options = array())
123
    {
124
        $this->create($from, $to, $options);
125
    }
126
127
    //--------------------------------------------------------------------
128
129
    /**
130
     * Sets the default constraint to be used in the system. Typically
131
     * for use with the 'resources' method.
132
     *
133
     * @param $constraint
134
     */
135
    public function setDefaultConstraint($constraint)
136
    {
137
        if (array_key_exists($constraint, $this->constraints)) {
138
            $this->default_constraint = $constraint;
139
        }
140
    }
141
142
    //--------------------------------------------------------------------
143
144
    /**
145
     * Registers a new constraint to be used internally. Useful for creating
146
     * very specific regex patterns, or simply to allow your routes to be
147
     * a tad more readable.
148
     *
149
     * Example:
150
     *      $route->registerConstraint('hero', '(^.*)');
151
     *
152
     *      $route->any('home/{hero}', 'heroes/journey');
153
     *
154
     *      // Route then looks like:
155
     *      $route['home/(^.*)'] = 'heroes/journey';
156
     *
157
     * @param      $name
158
     * @param      $pattern
159
     * @param bool $overwrite
160
     */
161
    public function registerConstraint($name, $pattern, $overwrite = false)
162
    {
163
        // Ensure consistency
164
        $name    = trim($name, '{} ');
165
        $pattern = '(' . trim($pattern, '() ') . ')';
166
167
        // Not here? Add it and leave...
168
        if (! array_key_exists($name, $this->constraints)) {
169
            $this->constraints[$name] = $pattern;
170
171
            return;
172
        }
173
174
        // Here? Then it exists. Should we overwrite it?
175
        if ($overwrite) {
176
            $this->constraints[$name] = $pattern;
177
        }
178
    }
179
180
    //--------------------------------------------------------------------
181
182
    //--------------------------------------------------------------------
183
    // Named Routes
184
    //--------------------------------------------------------------------
185
186
    /**
187
     * Returns the value of a named route. Useful for getting named
188
     * routes for use while building with site_url() or in templates
189
     * where you don't need to instantiate the route class.
190
     *
191
     * Example:
192
     *      $route->any('news', 'posts/index', ['as' => 'blog']);
193
     *
194
     *      // Returns http://mysite.com/news
195
     *      site_url( Route::named('blog') );
196
     *
197
     * @param  [type] $name [description]
198
     * @return [type]       [description]
199
     */
200
    public static function named($name)
201
    {
202
        if (isset(self::$names[$name])) {
203
            return self::$names[$name];
204
        }
205
206
        return null;
207
    }
208
209
    //--------------------------------------------------------------------
210
211
    //--------------------------------------------------------------------
212
    // Grouping Routes
213
    //--------------------------------------------------------------------
214
215
    /**
216
     * Group a series of routes under a single URL segment. This is handy
217
     * for grouping items into an admin area, like:
218
     *
219
     * Example:
220
     *     $route->group('admin', function() {
221
     *            $route->resources('users');
222
     *     });
223
     *
224
     * @param  string   $name     The name to group/prefix the routes with.
225
     * @param  \Closure $callback An anonymous function that allows you route inside of this group.
226
     * @return void
227
     */
228
    public function group($name, \Closure $callback)
229
    {
230
        $old_group = $this->group;
231
232
        // To register a route, we'll set a flag so that our router
233
        // so it will see the groupname.
234
        $this->group = ltrim($old_group . '/' . $name, '/');
235
236
        call_user_func($callback);
237
238
        // Make sure to clear the group name so we don't accidentally
239
        // group any ones we didn't want to.
240
        $this->group = $old_group;
241
    }
242
243
    //--------------------------------------------------------------------
244
245
    //--------------------------------------------------------------------
246
    // HTTP Verb-based routing
247
    //--------------------------------------------------------------------
248
    // Routing works here because, as the routes config file is read in,
249
    // the various HTTP verb-based routes will only be added to the in-memory
250
    // routes if it is a call that should respond to that verb.
251
    //
252
    // The options array is typically used to pass in an 'as' or var, but may
253
    // be expanded in the future. See the docblock for 'any' method above for
254
    // current list of globally available options.
255
    //
256
257
    /**
258
     * Specifies a single route to match for multiple HTTP Verbs.
259
     *
260
     * Example:
261
     *  $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
262
     *
263
     * @param array $verbs
264
     * @param       $from
265
     * @param       $to
266
     * @param array $options
267
     */
268
    public function match($verbs = [], $from, $to, $options = [])
269
    {
270
        foreach ($verbs as $verb) {
271
            $verb = strtolower($verb);
272
273
            $this->{$verb}($from, $to, $options);
274
        }
275
    }
276
277
    //--------------------------------------------------------------------
278
279
    /**
280
     * Specifies a route that is only available to GET requests.
281
     *
282
     * @param       $from
283
     * @param       $to
284
     * @param array $options
285
     */
286
    public function get($from, $to, $options = [])
287
    {
288 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') {
289
            $this->create($from, $to, $options);
290
        }
291
    }
292
293
    //--------------------------------------------------------------------
294
295
    /**
296
     * Specifies a route that is only available to POST requests.
297
     *
298
     * @param       $from
299
     * @param       $to
300
     * @param array $options
301
     */
302
    public function post($from, $to, $options = [])
303
    {
304 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
305
            $this->create($from, $to, $options);
306
        }
307
    }
308
309
    //--------------------------------------------------------------------
310
311
    /**
312
     * Specifies a route that is only available to PUT requests.
313
     *
314
     * @param       $from
315
     * @param       $to
316
     * @param array $options
317
     */
318
    public function put($from, $to, $options = [])
319
    {
320 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'PUT') {
321
            $this->create($from, $to, $options);
322
        }
323
    }
324
325
    //--------------------------------------------------------------------
326
327
    /**
328
     * Specifies a route that is only available to DELETE requests.
329
     *
330
     * @param       $from
331
     * @param       $to
332
     * @param array $options
333
     */
334
    public function delete($from, $to, $options = [])
335
    {
336 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'DELETE') {
337
            $this->create($from, $to, $options);
338
        }
339
    }
340
341
    //--------------------------------------------------------------------
342
343
    /**
344
     * Specifies a route that is only available to HEAD requests.
345
     *
346
     * @param       $from
347
     * @param       $to
348
     * @param array $options
349
     */
350
    public function head($from, $to, $options = [])
351
    {
352 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'HEAD') {
353
            $this->create($from, $to, $options);
354
        }
355
    }
356
357
    //--------------------------------------------------------------------
358
359
    /**
360
     * Specifies a route that is only available to PATCH requests.
361
     *
362
     * @param       $from
363
     * @param       $to
364
     * @param array $options
365
     */
366
    public function patch($from, $to, $options = [])
367
    {
368 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'PATCH') {
369
            $this->create($from, $to, $options);
370
        }
371
    }
372
373
    //--------------------------------------------------------------------
374
375
    /**
376
     * Specifies a route that is only available to OPTIONS requests.
377
     *
378
     * @param       $from
379
     * @param       $to
380
     * @param array $options
381
     */
382
    public function options($from, $to, $options = [])
383
    {
384 View Code Duplication
        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
385
            $this->create($from, $to, $options);
386
        }
387
    }
388
389
    //--------------------------------------------------------------------
390
391
    /**
392
     * Creates a collections of HTTP-verb based routes for a controller.
393
     *
394
     * Possible Options:
395
     *      'controller'    - Customize the name of the controller used in the 'to' route
396
     *      'module'        - Prepend a module name to the generate 'to' routes
397
     *      'constraint'    - The regex used by the Router. Defaults to '(:any)'
398
     *
399
     * Example:
400
     *      $route->resources('photos');
401
     *
402
     *      // Generates the following routes:
403
     *      HTTP Verb | Path        | Action        | Used for...
404
     *      ----------+-------------+---------------+-----------------
405
     *      GET         /photos             index           display a list of photos
406
     *      GET         /photos/new         creation_form   return an HTML form for creating a new photo
407
     *      GET         /photos/{id}        show            display a specific photo
408
     *      GET         /photos/{id}/edit   editing_form    return an HTML form for editing the photo
409
     *      POST        /photos             create          create a new photo
410
     *      PUT         /photos/{id}        update          update an existing photo
411
     *      DELETE      /photos/{id}/delete delete          delete an existing photo
412
     *
413
     * @param  string $name    The name of the controller to route to.
414
     * @param  array  $options An list of possible ways to customize the routing.
415
     */
416
    public function resources($name, $options = [])
417
    {
418
        // In order to allow customization of the route the
419
        // resources are sent to, we need to have a new name
420
        // to store the values in.
421
        $new_name = $name;
422
423
        // If a new controller is specified, then we replace the
424
        // $name value with the name of the new controller.
425
        if (isset($options['controller'])) {
426
            $new_name = $options['controller'];
427
        }
428
429
        // If a new module was specified, simply put that path
430
        // in front of the controller.
431
        if (isset($options['module'])) {
432
            $new_name = $options['module'] . '/' . $new_name;
433
        }
434
435
        // In order to allow customization of allowed id values
436
        // we need someplace to store them.
437
        $id = isset($this->constraints[$this->default_constraint]) ? $this->constraints[$this->default_constraint] :
438
            '(:any)';
439
440
        if (isset($options['constraint'])) {
441
            $id = $options['constraint'];
442
        }
443
444
        $this->get($name, $new_name . '/list_all', $options);
445
        $this->get($name . '/' . $id, $new_name . '/show/$1', $options);
446
        $this->post($name, $new_name . '/create', $options);
447
        $this->put($name . '/' . $id, $new_name . '/update/$1', $options);
448
        $this->delete($name . '/' . $id, $new_name . '/delete/$1', $options);
449
        $this->options($name, $new_name . '/index', $options);
450
    }
451
452
    //--------------------------------------------------------------------
453
454
    /**
455
     * Lets the system know about different 'areas' within the site, like
456
     * the admin area, that maps to certain controllers.
457
     *
458
     * @param  string $area       The name of the area.
459
     * @param  string $controller The controller name to look for.
460
     * @param         $options
461
     */
462
    public function area($area, $controller = null, $options = [])
463
    {
464
        // No controller? Match the area name.
465
        $controller = is_null($controller) ? $area : $controller;
466
467
        // Save the area so we can recognize it later.
468
        self::$areas[$area] = $controller;
469
470
        // Create routes for this area.
471
        $this->create($area . '/(:any)/(:any)/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3/$4/$5', $options);
472
        $this->create($area . '/(:any)/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3/$4', $options);
473
        $this->create($area . '/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3', $options);
474
        $this->create($area . '/(:any)/(:any)', '$1/' . $controller . '/$2', $options);
475
        $this->create($area . '/(:any)', '$1/' . $controller, $options);
476
    }
477
478
    //--------------------------------------------------------------------
479
480
    /**
481
     * Returns the name of the area based on the controller name.
482
     *
483
     * @param  string $controller The name of the controller
484
     * @return string             The name of the corresponding area
485
     */
486
    public static function getAreaName($controller)
487
    {
488
        foreach (self::$areas as $area => $cont) {
489
            if ($controller == $cont) {
490
                return $area;
491
            }
492
        }
493
494
        return null;
495
    }
496
497
    //--------------------------------------------------------------------
498
499
    /**
500
     * Limits the routes to a specified ENVIRONMENT or they won't run.
501
     *
502
     * @param $env
503
     * @param callable $callback
504
     *
505
     * @return bool|null
506
     */
507
    public function environment($env, \Closure $callback)
508
    {
509
        if (ENVIRONMENT == $env)
510
        {
511
            call_user_func($callback);
512
            return true;
513
        }
514
515
        return null;
516
    }
517
518
    //--------------------------------------------------------------------
519
520
521
522
    /**
523
     * Allows you to easily block access to any number of routes by setting
524
     * that route to an empty path ('').
525
     *
526
     * Example:
527
     *     Route::block('posts', 'photos/(:num)');
528
     *
529
     *     // Same as...
530
     *     $route['posts']          = '';
531
     *     $route['photos/(:num)']  = '';
532
     */
533
    public function block()
534
    {
535
        $paths = func_get_args();
536
537
        if (! is_array($paths) || ! count($paths)) {
538
            return;
539
        }
540
541
        foreach ($paths as $path) {
542
            $this->create($path, '');
543
        }
544
    }
545
546
    //--------------------------------------------------------------------
547
548
    /**
549
     * Empties all named and un-named routes from the system.
550
     *
551
     * @return void
552
     */
553
    public function reset()
554
    {
555
        $this->routes = array();
556
        $this->group  = null;
557
        self::$names  = array();
558
        self::$areas  = array();
559
    }
560
561
    //--------------------------------------------------------------------
562
563
    //--------------------------------------------------------------------
564
    // Private Methods
565
    //--------------------------------------------------------------------
566
567
    /**
568
     * Does the heavy lifting of creating an actual route. You must specify
569
     * the request method(s) that this route will work for. They can be separated
570
     * by a pipe character "|" if there is more than one.
571
     *
572
     * @param  string $from
573
     * @param  array  $to
574
     * @param array   $options
575
     *
576
     * @return array          The built route.
577
     */
578
    private function create($from, $to, $options = array())
579
    {
580
        $prefix = is_null($this->group) ? '' : $this->group . '/';
581
582
        $from = $prefix . $from;
583
584
        // Are we saving the name for this one?
585
        if (isset($options['as']) && !empty($options['as'])) {
586
            self::$names[$options['as']] = $from;
587
        }
588
589
        // Limiting to subdomains?
590
        if (isset($options['subdomain']) && !empty($options['subdomain'])) {
591
            // If we don't match the current subdomain, then
592
            // we don't need to add the route.
593
            if (!$this->checkSubdomains($options['subdomain'])) {
594
                return;
595
            }
596
        }
597
598
        // Are we offsetting the parameters?
599
        // If so, take care of them here in one
600
        // fell swoop.
601
        if (isset($options['offset'])) {
602
            // Get a constant string to work with.
603
            $to = preg_replace('/(\$\d+)/', '$X', $to);
604
605
            for ($i = (int)$options['offset'] + 1; $i < (int)$options['offset'] + 7; $i ++) {
606
                $to = preg_replace_callback(
607
                    '/\$X/',
608
                    function ($m) use ($i) {
609
                        return '$' . $i;
610
                    },
611
                    $to,
612
                    1
613
                );
614
            }
615
        }
616
617
        // Convert any custom constraints to the CI/pattern equivalent
618
        foreach ($this->constraints as $name => $pattern) {
619
            $from = str_replace('{' . $name . '}', $pattern, $from);
620
        }
621
622
        $this->routes[$from] = $to;
623
    }
624
625
    //--------------------------------------------------------------------
626
627
    /**
628
     * Compares the subdomain(s) passed in against the current subdomain
629
     * on this page request.
630
     *
631
     * @param $subdomains
632
     * @return bool
633
     */
634
    private function checkSubdomains($subdomains)
635
    {
636
        if (is_null($this->current_subdomain)) {
637
            $this->determineCurrentSubdomain();
638
        }
639
640
        if (!is_array($subdomains)) {
641
            $subdomains = array($subdomains);
642
        }
643
644
        $matched = false;
645
646
        array_walk(
647
            $subdomains,
648
            function ($subdomain) use (&$matched) {
649
                if ($subdomain == $this->current_subdomain || $subdomain == '*') {
650
                    $matched = true;
651
                }
652
            }
653
        );
654
655
        return $matched;
656
    }
657
658
    //--------------------------------------------------------------------
659
660
    /**
661
     * Examines the HTTP_HOST to get a best match for the subdomain. It
662
     * won't be perfect, but should work for our needs.
663
     */
664
    private function determineCurrentSubdomain()
665
    {
666
        $parsedUrl = parse_url($_SERVER['HTTP_HOST']);
667
668
        $host = explode('.', $parsedUrl['host']);
669
670
        // If we only have 2 parts, then we don't have a subdomain.
671
        // This won't be totally accurate, since URL's like example.co.uk
672
        // would still pass, but it helps to separate the chaff...
673
        if (!is_array($host) || count($host) == 2) {
674
            // Set it to false so we don't make it back here again.
675
            $this->current_subdomain = false;
676
            return;
677
        }
678
679
        // Now, we'll simply take the first element of the array. This should
680
        // be fine even in cases like example.co.uk, since they won't be looking
681
        // for 'example' when they try to match the subdomain, in most all cases.
682
        $this->current_subdomain = array_shift($host);
683
    }
684
    //--------------------------------------------------------------------
685
686
}
687