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.
Completed
Push — master ( 4a7ff2...206530 )
by やかみ
02:05
created

Route   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 478
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 5.35%

Importance

Changes 20
Bugs 0 Features 0
Metric Value
c 20
b 0
f 0
dl 0
loc 478
ccs 10
cts 187
cp 0.0535
rs 5.5244
wmc 74
lcom 1
cbo 9

11 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 26 8
F dispatch() 0 116 19
A getController() 0 10 3
A getAction() 0 10 2
A getParams() 0 6 1
C getMethodInstances() 0 37 8
A getUri() 0 4 1
A getRoutes() 0 4 1
D parseRoutes() 0 89 20
A parseArgv() 0 5 2
D url() 0 30 9

How to fix   Complexity   

Complex Class

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
2
/**
3
 * Kotori.php
4
 *
5
 * A Tiny Model-View-Controller PHP Framework
6
 *
7
 * This content is released under the Apache 2 License
8
 *
9
 * Copyright (c) 2015-2017 Kotori Technology. All rights reserved.
10
 *
11
 * Licensed under the Apache License, Version 2.0 (the "License");
12
 * you may not use this file except in compliance with the License.
13
 * You may obtain a copy of the License at
14
 *
15
 *     http://www.apache.org/licenses/LICENSE-2.0
16
 *
17
 * Unless required by applicable law or agreed to in writing, software
18
 * distributed under the License is distributed on an "AS IS" BASIS,
19
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
 * See the License for the specific language governing permissions and
21
 * limitations under the License.
22
 */
23
24
/**
25
 * Route class
26
 *
27
 * Parses URIs and determines routing
28
 *
29
 * @package     Kotori
30
 * @subpackage  Http
31
 * @author      Kokororin
32
 * @link        https://kotori.love
33
 */
34
namespace Kotori\Http;
35
36
use Kotori\Core\Container;
37
use Kotori\Core\Helper;
38
use Kotori\Core\Middleware;
39
use Kotori\Debug\Hook;
40
use Kotori\Exception\ConfigException;
41
use Kotori\Exception\NotFoundException;
42
use Kotori\Exception\RouteNotFoundException;
43
use ReflectionClass;
44
use Symfony\Component\Finder\Finder;
45
use zpt\anno\Annotations;
46
47
class Route
48
{
49
    /**
50
     * Controllers Array
51
     *
52
     * @var array
53
     */
54
    protected $controllers = [];
55
56
    /**
57
     * Current controller
58
     *
59
     * @var string
60
     */
61
    protected $controller;
62
63
    /**
64
     * Current action
65
     *
66
     * @var string
67
     */
68
    protected $action;
69
70
    /**
71
     * Current URI string
72
     *
73
     * @var mixed
74
     */
75
    protected $uri = '';
76
77
    /**
78
     * Parsed URI Array
79
     *
80
     * @var array
81
     */
82
    protected $uris = [];
83
84
    /**
85
     * Parsed params
86
     *
87
     * @var array
88
     */
89
    protected $params = [];
90
91
    /**
92
     * Parsed routes
93
     *
94
     * @var array
95
     */
96
    protected $routes = [];
97
98
    /**
99
     * Class constructor
100
     *
101
     * Initialize route class.
102
     *
103
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
104
     */
105 1
    public function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_GET 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...
Coding Style introduced by
__construct 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...
106
    {
107 1
        if (Container::get('request')->isCli()) {
108 1
            $this->uri = $this->parseArgv();
109
        } else {
110
            if (isset($_GET['_i'])) {
111
                $_SERVER['PATH_INFO'] = $_GET['_i'];
112
            }
113
114
            $_SERVER['PATH_INFO'] = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO']
115
            : (isset($_SERVER['ORIG_PATH_INFO']) ? $_SERVER['ORIG_PATH_INFO']
116
                : (isset($_SERVER['REDIRECT_PATH_INFO']) ? $_SERVER['REDIRECT_PATH_INFO'] : ''));
117
118
            $this->uri = $_SERVER['PATH_INFO'];
119
        }
120
121 1
        if (substr($this->uri, 0, 1) == '/') {
122
            $this->uri = ltrim($this->uri, '/');
123
        }
124
125 1
        if (trim($this->uri, '/') == '') {
126
            $this->uri = '/';
127
        }
128
129 1
        Hook::listen(__CLASS__);
130 1
    }
131
132
    /**
133
     * Map URL to controller and action
134
     *
135
     * @return void
136
     *
137
     * @throws \Kotori\Exception\RouteNotFoundException
138
     * @throws \Kotori\Exception\NotFoundException
139
     */
140
    public function dispatch()
0 ignored issues
show
Coding Style introduced by
dispatch 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...
Coding Style introduced by
dispatch uses the super-global variable $_GET 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...
Coding Style introduced by
dispatch uses the super-global variable $_REQUEST 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...
Coding Style introduced by
dispatch uses the super-global variable $_POST 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...
Coding Style introduced by
dispatch uses the super-global variable $_COOKIE 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...
141
    {
142
        if (strtolower(Container::get('config')->get('url_mode')) == 'query_string') {
143
            $this->uri = explode('?', $this->uri, 2);
144
            $_SERVER['QUERY_STRING'] = isset($this->uri[1]) ? $this->uri[1] : '';
145
            $this->uri = $this->uri[0];
146
            parse_str($_SERVER['QUERY_STRING'], $_GET);
147
        }
148
149
        if ($this->uri == 'favicon.ico') {
150
            return Container::get('response')->setStatus(404);
151
        }
152
153
        Middleware::register('before_route');
154
        $parsedRoute = $this->parseRoutes($this->uri);
155
        Middleware::register('after_route');
156
157
        if ($parsedRoute) {
158
            $this->uri = $parsedRoute;
159
        } else {
160
            if (Container::get('request')->isOptions()) {
161
                Container::get('response')->setStatus(204);
162
                exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method dispatch() 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...
163
            }
164
165
            throw new RouteNotFoundException('Request URI ' . $this->uri . ' is not Matched by any route.');
166
        }
167
168
        $this->uris = ($this->uri != '') ? explode('/', trim($this->uri, '/')) : [];
169
170
        // Clean uris
171
        foreach ($this->uris as $key => $value) {
172
            if ($value == '') {
173
                unset($this->uris[$key]);
174
            }
175
        }
176
177
        $this->uris = array_merge($this->uris);
178
179
        $this->controller = $this->getController();
180
        $this->action = $this->getAction();
181
182
        // If is already initialized
183
        $prefix = Container::get('config')->get('namespace_prefix');
184
185
        $controllerClassName = $prefix . 'controllers\\' . $this->controller;
186
187
        Middleware::register('before_controller');
188
189
        if (isset($this->controllers[$this->controller])) {
190
            $class = $this->controllers[$this->controller];
191
        } else {
192
            $constructInstances = $this->getMethodInstances($controllerClassName);
193
            if (!$constructInstances) {
194
                $class = new $controllerClassName();
195
            } else {
196
                $reflect = new ReflectionClass($controllerClassName);
197
                $class = $reflect->newInstanceArgs($constructInstances);
198
            }
199
200
            $this->controllers[$this->controller] = $class;
201
        }
202
203
        Middleware::register('after_controller');
204
205
        if (!class_exists($controllerClassName)) {
206
            throw new NotFoundException('Request Controller ' . $this->controller . ' is not Found.');
207
        }
208
209
        if (!method_exists($class, $this->action)) {
210
            throw new NotFoundException('Request Action ' . $this->action . ' is not Found.');
211
        }
212
213
        $callback = [$class, $this->action];
214
        if (!is_callable($callback)) {
215
            throw new NotFoundException($controllerClassName . '::' . $this->action . '() is not callable');
216
        }
217
218
        // Parse params from uri
219
        $this->params = $this->getParams();
220
221
        // Do some final cleaning of the params
222
        $_GET = array_merge($this->params, $_GET);
223
        $_REQUEST = array_merge($_POST, $_GET, $_COOKIE);
224
225
        if (Container::get('config')->get('app_debug')) {
226
            Container::get('response')->setHeader('X-Kotori-Hash', call_user_func(function () {
227
                $lockFile = Helper::getComposerVendorPath() . '/../composer.lock';
228
                if (!Helper::isFile($lockFile)) {
229
                    return 'unknown';
230
                } else {
231
                    $lockData = file_get_contents($lockFile);
232
                    $lockData = json_decode($lockData, true);
233
                    foreach ($lockData['packages'] as $package) {
234
                        if ($package['name'] == 'kokororin/kotori-php') {
235
                            return substr($package['source']['reference'], 0, 6);
236
                        }
237
                    }
238
                }
239
240
                return 'unknown';
241
            }));
242
        }
243
244
        Middleware::register('before_action');
245
        // Call the requested method
246
247
        $methodInstances = $this->getMethodInstances($callback[0], $callback[1]);
248
        if (!$methodInstances) {
249
            call_user_func_array($callback, $this->params);
250
        } else {
251
            call_user_func_array($callback, $methodInstances);
252
        }
253
254
        Middleware::register('after_action');
255
    }
256
257
    /**
258
     * Returns the controller name
259
     *
260
     * @return      string
261
     *
262
     * @throws \Kotori\Exception\NotFoundException
263
     */
264
    public function getController()
265
    {
266
        if (isset($this->uris[0]) && '' !== $this->uris[0]) {
267
            $_controller = $this->uris[0];
268
        } else {
269
            throw new NotFoundException('Cannot dispatch controller name.');
270
        }
271
272
        return strip_tags($_controller);
273
    }
274
275
    /**
276
     * Returns the action name
277
     *
278
     * @return      string
279
     *
280
     * @throws \Kotori\Exception\NotFoundException
281
     */
282
    public function getAction()
283
    {
284
        if (isset($this->uris[1])) {
285
            $_action = $this->uris[1];
286
        } else {
287
            throw new NotFoundException('Cannot dispatch action name.');
288
        }
289
290
        return strip_tags($_action);
291
    }
292
293
    /**
294
     * Returns the request params
295
     *
296
     * @return array
297
     */
298
    public function getParams()
299
    {
300
        $params = $this->uris;
301
        unset($params[0], $params[1]);
302
        return array_merge($params);
303
    }
304
305
    /**
306
     * Returns the request params instances
307
     *
308
     * @param  mixed  $class
309
     * @param  string $methodName
310
     * @return mixed
311
     *
312
     * @throws \Kotori\Exception\NotFoundException
313
     */
314
    private function getMethodInstances($class, $methodName = '__construct')
315
    {
316
        if (is_object($class)) {
317
            $reflectClass = new ReflectionClass(get_class($class));
318
        } else {
319
            $reflectClass = new ReflectionClass($class);
320
        }
321
322
        $instances = [];
323
324
        if ($reflectClass->hasMethod($methodName)) {
325
            $reflectMethod = $reflectClass->getMethod($methodName);
326
327
            $params = $reflectMethod->getParameters();
328
329
            $hasDI = false;
330
331
            if (count($params) > 0) {
332
                foreach ($params as $param) {
333
                    $paramClass = $param->getClass();
334
                    if ($paramClass) {
335
                        $paramClassName = $paramClass->getName();
336
                        array_push($instances, Container::getByClassName($paramClassName));
337
                        $hasDI = true;
338
                    } elseif ($hasDI) {
339
                        throw new NotFoundException('Dependency Injection cannot work with normal params');
340
                    }
341
                }
342
            }
343
        }
344
345
        if ($hasDI) {
0 ignored issues
show
Bug introduced by
The variable $hasDI does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
346
            return $instances;
347
        }
348
349
        return false;
350
    }
351
352
    /**
353
     * Returns the URI
354
     *
355
     * @return string
356
     */
357
    public function getUri()
358
    {
359
        return $this->uri;
360
    }
361
362
    /**
363
     * Returns routes
364
     *
365
     * @return array
366
     */
367
    public function getRoutes()
368
    {
369
        return $this->routes;
370
    }
371
372
    /**
373
     * Parse Routes
374
     *
375
     * Matches any routes that may exist in URL_ROUTE array
376
     * against the URI to determine if the class/method need to be remapped.
377
     *
378
     * @param  string $uri
379
     * @return string
380
     */
381
    protected function parseRoutes($uri)
0 ignored issues
show
Coding Style introduced by
parseRoutes 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...
382
    {
383
        if (Container::get('config')->get('url_route_annotation')) {
384
            $finder = new Finder();
385
            $finder
386
                ->in(Container::get('config')->get('app_full_path') . '/controllers')
387
                ->name('*.php');
388
            $controllerNamespaces = [];
389
            foreach ($finder as $file) {
390
                array_push($controllerNamespaces, '\\' . Container::get('config')->get('namespace_prefix') . 'controllers\\' . $file->getBasename('.' . $file->getExtension()));
391
            }
392
393
            foreach ($controllerNamespaces as $namespace) {
394
                $classReflector = new ReflectionClass($namespace);
395
                $classAnnotations = new Annotations($classReflector);
0 ignored issues
show
Unused Code introduced by
$classAnnotations 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...
396
397
                foreach ($classReflector->getMethods() as $methodReflector) {
398
                    $methodAnnotations = new Annotations($methodReflector);
399
                    if (!isset($methodAnnotations['route'])) {
400
                        continue;
401
                    } else {
402
                        $routeAnnotations = $methodAnnotations['route'];
403
                        if (!isset($routeAnnotations['uri'])) {
404
                            throw new ConfigException('Route annotations error');
405
                        }
406
407
                        $controllerName = $classReflector->getShortName();
408
                        $actionName = $methodReflector->getName();
0 ignored issues
show
Bug introduced by
Consider using $methodReflector->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
409
                        $route = $controllerName . '/' . $actionName;
410
                        if (isset($routeAnnotations['params'])) {
411
                            $route .= '/' . $routeAnnotations['params'];
412
                        }
413
414
                        if (!isset($routeAnnotations['method'])) {
415
                            $this->routes[$routeAnnotations['uri']] = $route;
416
                        } else {
417
                            $this->routes[$routeAnnotations['uri']][$routeAnnotations['method']] = $route;
418
                        }
419
420
                        unset($route);
421
                    }
422
                }
423
            }
424
        } else {
425
            $this->routes = Container::get('config')->get('url_route');
426
        }
427
428
        $hostName = Container::get('request')->getHostName();
429
430
        if (isset($this->routes[$hostName])) {
431
            $this->routes = $this->routes[$hostName];
432
        }
433
434
        // Get HTTP verb
435
        $httpVerb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
436
437
        if (null != $this->routes) {
438
            foreach ($this->routes as $key => $val) {
439
                // Check if route format is using HTTP verbs
440
                if (is_array($val)) {
441
                    $val = array_change_key_case($val, CASE_LOWER);
442
                    if (isset($val[$httpVerb])) {
443
                        $val = $val[$httpVerb];
444
                    } else {
445
                        continue;
446
                    }
447
                }
448
449
                // Does the RegEx match?
450
                if (preg_match('#^' . $key . '$#', $uri, $matches)) {
451
                    // Are we using callbacks to process back-references?
452
                    if (!is_string($val) && is_callable($val)) {
453
                        // Remove the original string from the matches array.
454
                        array_shift($matches);
455
456
                        // Execute the callback using the values in matches as its parameters.
457
                        $val = call_user_func_array($val, $matches);
458
                    }
459
                    // Are we using the default routing method for back-references?
460
                    elseif (strpos($val, '$') !== false && strpos($key, '(') !== false) {
461
                        $val = preg_replace('#^' . $key . '$#', $val, $uri);
462
                    }
463
464
                    return $val;
465
                }
466
            }
467
        }
468
469
    }
470
471
    /**
472
     * Parse CLI arguments
473
     *
474
     * Take each command line argument and assume it is a URI segment.
475
     *
476
     * @return  string
477
     */
478 1
    protected function parseArgv()
0 ignored issues
show
Coding Style introduced by
parseArgv 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...
479
    {
480 1
        $args = array_slice($_SERVER['argv'], 1);
481 1
        return $args ? implode('/', $args) : '';
482
    }
483
484
    /**
485
     * Build Full URL
486
     *
487
     * @param  string $uri
488
     * @param  string $module
489
     * @return string
490
     *
491
     * @throws \Kotori\Exception\ConfigException
492
     */
493
    public function url($uri = '', $module = null)
494
    {
495
        if ($module != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $module of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
496
            $appNames = Container::get('config')->get('app_name');
497
            if (is_array($appNames)) {
498
                foreach ($appNames as &$appName) {
499
                    $appName = str_replace('./', '', $appName);
500
                }
501
502
                $appNames = array_flip($appNames);
503
                $baseUrl = $appNames[$module];
504
                $baseUrl = '//' . $baseUrl . '/';
505
            }
506
        } else {
507
            $baseUrl = Container::get('request')->getBaseUrl();
508
        }
509
510
        $uri = is_array($uri) ? implode('/', $uri) : trim($uri, '/');
511
        $prefix = $baseUrl . 'index.php?_i=';
0 ignored issues
show
Bug introduced by
The variable $baseUrl does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
512
513
        switch (strtolower(Container::get('config')->get('url_mode'))) {
514
            case 'path_info':
515
                return $uri == '' ? rtrim($baseUrl, '/') : $baseUrl . $uri;
516
            case 'query_string':
517
                return $uri == '' ? rtrim($baseUrl, '/') : $prefix . $uri;
518
            default:
519
                throw new ConfigException('`url_mode` Config ERROR');
520
        }
521
522
    }
523
524
}
525