Test Failed
Push — master ( 7385cb...affc13 )
by Mikael
01:28
created

Router   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 360
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 40
c 2
b 0
f 0
lcom 1
cbo 3
dl 0
loc 360
ccs 0
cts 87
cp 0
rs 8.2608

16 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 28 5
C handle() 0 25 7
A handleInternal() 0 9 2
C load() 0 37 11
A any() 0 14 4
A add() 0 4 1
A always() 0 4 1
A all() 0 4 1
A get() 0 4 1
A post() 0 4 1
A put() 0 4 1
A delete() 0 4 1
A addInternal() 0 7 1
A getLastRoute() 0 4 1
A getAll() 0 4 1
A getInternal() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Router 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 Router, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Anax\Route;
4
5
use Anax\DI\InjectionAwareInterface;
6
use Anax\DI\InjectionAwareTrait;
7
use \Anax\Configure\ConfigureInterface;
8
use \Anax\Configure\Configure2Trait;
9
10
/**
11
 * A container for routes.
12
 */
13
class Router implements
14
    InjectionAwareInterface,
15
    ConfigureInterface
16
{
17
    use InjectionAwareTrait;
18
    use Configure2Trait {
19
        configure as protected configure2;
20
    }
21
22
23
24
    /**
25
     * @var array       $routes         all the routes.
26
     * @var array       $internalRoutes all internal routes.
27
     * @var null|string $lastRoute      last route that was matched and called.
28
     */
29
    private $routes         = [];
30
    private $internalRoutes = [];
31
    private $lastRoute      = null;
32
33
34
35
    /**
36
     * Load and apply configurations.
37
     *
38
     * @param array|string $what is an array with key/value config options
39
     *                           or a file to be included which returns such
40
     *                           an array.
41
     *
42
     * @return self
43
     */
44
    public function configure($what)
45
    {
46
        $this->configure2($what);
47
        $includes = $this->getConfig("routeFiles", []);
48
        $items    = $this->getConfig("items", []);
49
        $config = array_merge($includes, $items);
50
51
        // Add a sort field if missing, to maintain order
52
        // when sorting
53
        $sort = 1;
54
        array_walk($config, function(&$item) use(&$sort) {
55
            $item["sort"] = (isset($item["sort"]))
56
                ? $item["sort"]
57
                : $sort++;
58
        });
59
        uasort($config, function($item1, $item2) {
60
            if ($item1["sort"] === $item2["sort"]) {
61
                return 0;
62
            }
63
            return ($item1["sort"] < $item2["sort"]) ? -1 : 1;
64
        });
65
66
        foreach ($config as $route) {
67
            $this->load($route);
68
        }
69
70
        return $this;
71
    }
72
73
74
75
    /**
76
     * Handle the routes and match them towards the request, dispatch them
77
     * when a match is made. Each route handler may throw exceptions that
78
     * may redirect to an internal route for error handling.
79
     * Several routes can match and if the routehandler does not break
80
     * execution flow, the route matching will carry on.
81
     * Only the last routehandler will get its return value returned further.
82
     *
83
     * @param string $path    the path to find a matching handler for.
84
     * @param string $method  the request method to match.
85
     *
86
     * @return mixed content returned from route.
87
     */
88
    public function handle($path, $method = null)
89
    {
90
        try {
91
            $match = false;
92
            foreach ($this->routes as $route) {
93
                if ($route->match($path, $method)) {
94
                    $this->lastRoute = $route->getRule();
95
                    $match = true;
96
                    $results = $route->handle($this->di);
97
                }
98
            }
99
100
            if ($match) {
101
                return $results;
0 ignored issues
show
Bug introduced by
The variable $results 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...
102
            }
103
104
            $this->handleInternal("404");
105
        } catch (ForbiddenException $e) {
106
            $this->handleInternal("403");
107
        } catch (NotFoundException $e) {
108
            $this->handleInternal("404");
109
        } catch (InternalErrorException $e) {
110
            $this->handleInternal("500");
111
        }
112
    }
113
114
115
116
    /**
117
     * Handle an internal route, the internal routes are not exposed to the
118
     * end user.
119
     *
120
     * @param string $rule for this route.
121
     *
122
     * @return void
123
     *
124
     * @throws \Anax\Route\NotFoundException
125
     */
126
    public function handleInternal($rule)
127
    {
128
        if (!isset($this->internalRoutes[$rule])) {
129
            throw new NotFoundException("No internal route to handle: " . $rule);
130
        }
131
        $route = $this->internalRoutes[$rule];
132
        $this->lastRoute = $rule;
133
        $route->handle();
134
    }
135
136
137
138
    /**
139
     * Load routes from a config file, the file should return an array with
140
     * details of the routes. These details are used to create the routes.
141
     *
142
     * @param array $route details on the route.
143
     *
144
     * @return self
145
     */
146
    public function load($route)
147
    {
148
        $mount = isset($route["mount"]) ? rtrim($route["mount"], "/") : null;
149
        if (!array_key_exists("mount", $route)) {
150
            // To fix compatibility with new configuration
151
            // where configuration item needs "mount" to be
152
            // used and not ignored.
153
            return $this;
154
        }
155
156
        $config = $route;
157
        $file = isset($route["file"]) ? $route["file"] : null;
158
        if ($file && is_readable($file)) {
159
            $config = require($file);
160
        }
161
162
        $routes = isset($config["routes"]) ? $config["routes"] : [];
163
        foreach ($routes as $route) {
164
            $path = isset($mount)
165
                ? $mount . "/" . $route["path"]
166
                : $route["path"];
167
168
            if (isset($route["internal"]) && $route["internal"]) {
169
                $this->addInternal($path, $route["callable"]);
170
                continue;
171
            }
172
173
            $this->any(
174
                $route["requestMethod"],
175
                $path,
176
                $route["callable"],
177
                $route["info"]
178
            );
179
        }
180
181
        return $this;
182
    }
183
184
185
186
    /**
187
     * Add a route with a request method, a path rule to match and an action
188
     * as the callback. Adding several path rules (array) results in several
189
     * routes being created.
190
     *
191
     * @param null|string|array    $method as a valid request method.
192
     * @param null|string|array    $rule   path rule for this route.
193
     * @param null|string|callable $action to implement a handler for the route.
194
     * @param null|string          $info   about the route.
195
     *
196
     * @return class|array as new route(s), class if one added, else array.
197
     */
198
    public function any($method, $rule, $action, $info = null)
199
    {
200
        $rules = is_array($rule) ? $rule : [$rule];
201
202
        $routes = [];
203
        foreach ($rules as $val) {
204
            $route = new Route();
205
            $route->set($val, $action, $method, $info);
206
            $routes[] = $route;
207
            $this->routes[] = $route;
208
        }
209
210
        return count($routes) === 1 ? $routes[0] : $routes;
211
    }
212
213
214
215
    /**
216
     * Add a route to the router by rule(s) and a callback.
217
     *
218
     * @param null|string|array    $rule   for this route.
219
     * @param null|string|callable $action a callback handler for the route.
220
     *
221
     * @return class|array as new route(s), class if one added, else array.
222
     */
223
    public function add($rule, $action = null)
224
    {
225
        return $this->any(null, $rule, $action);
226
    }
227
228
229
230
    /**
231
    * Add a default route which will be applied for any path.
232
     *
233
     * @param string|callable $action a callback handler for the route.
234
     *
235
     * @return class as new route.
236
     */
237
    public function always($action)
238
    {
239
        return $this->any(null, null, $action);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->any(null, null, $action); of type Anax\Route\class|array adds the type array to the return on line 239 which is incompatible with the return type documented by Anax\Route\Router::always of type Anax\Route\class.
Loading history...
240
    }
241
242
243
244
    /**
245
     * Add a default route which will be applied for any path, if the choosen
246
     * request method is matching.
247
     *
248
     * @param null|string|array    $method as request methods
249
     * @param null|string|callable $action a callback handler for the route.
250
     *
251
     * @return class|array as new route(s), class if one added, else array.
252
     */
253
    public function all($method, $action)
254
    {
255
        return $this->any($method, null, $action);
256
    }
257
258
259
260
    /**
261
     * Shortcut to add a GET route.
262
     *
263
     * @param null|string|array    $method as request methods
0 ignored issues
show
Bug introduced by
There is no parameter named $method. 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...
264
     * @param null|string|callable $action a callback handler for the route.
265
     *
266
     * @return class|array as new route(s), class if one added, else array.
267
     */
268
    public function get($rule, $action)
269
    {
270
        return $this->any(["GET"], $rule, $action);
271
    }
272
273
274
275
    /**
276
    * Shortcut to add a POST route.
277
     *
278
     * @param null|string|array    $method as request methods
0 ignored issues
show
Bug introduced by
There is no parameter named $method. 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...
279
     * @param null|string|callable $action a callback handler for the route.
280
     *
281
     * @return class|array as new route(s), class if one added, else array.
282
     */
283
    public function post($rule, $action)
284
    {
285
        return $this->any(["POST"], $rule, $action);
286
    }
287
288
289
290
    /**
291
    * Shortcut to add a PUT route.
292
     *
293
     * @param null|string|array    $method as request methods
0 ignored issues
show
Bug introduced by
There is no parameter named $method. 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...
294
     * @param null|string|callable $action a callback handler for the route.
295
     *
296
     * @return class|array as new route(s), class if one added, else array.
297
     */
298
    public function put($rule, $action)
299
    {
300
        return $this->any(["PUT"], $rule, $action);
301
    }
302
303
304
305
    /**
306
    * Shortcut to add a DELETE route.
307
     *
308
     * @param null|string|array    $method as request methods
0 ignored issues
show
Bug introduced by
There is no parameter named $method. 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...
309
     * @param null|string|callable $action a callback handler for the route.
310
     *
311
     * @return class|array as new route(s), class if one added, else array.
312
     */
313
    public function delete($rule, $action)
314
    {
315
        return $this->any(["DELETE"], $rule, $action);
316
    }
317
318
319
320
    /**
321
     * Add an internal route to the router, this route is not exposed to the
322
     * browser and the end user.
323
     *
324
     * @param string               $rule   for this route
325
     * @param null|string|callable $action a callback handler for the route.
326
     *
327
     * @return class|array as new route(s), class if one added, else array.
328
     */
329
    public function addInternal($rule, $action)
330
    {
331
        $route = new Route();
332
        $route->set($rule, $action);
333
        $this->internalRoutes[$rule] = $route;
334
        return $route;
335
    }
336
337
338
339
    /**
340
     * Get the route for the last route that was handled.
341
     *
342
     * @return mixed
343
     */
344
    public function getLastRoute()
345
    {
346
        return $this->lastRoute;
347
    }
348
349
350
351
    /**
352
     * Get all routes.
353
     *
354
     * @return array with all routes.
355
     */
356
    public function getAll()
357
    {
358
        return $this->routes;
359
    }
360
361
362
363
    /**
364
     * Get all internal routes.
365
     *
366
     * @return array with internal routes.
367
     */
368
    public function getInternal()
369
    {
370
        return $this->internalRoutes;
371
    }
372
}
373