Passed
Push — master ( 71aeb5...466fc7 )
by Mikael
01:35
created

Router::load()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
ccs 0
cts 19
cp 0
rs 8.439
cc 6
eloc 17
nc 10
nop 1
crap 42
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\ConfigureTrait;
9
10
/**
11
 * A container for routes.
12
 */
13
class Router implements
14
    InjectionAwareInterface,
15
    ConfigureInterface
16
{
17
    use InjectionAwareTrait;
18
    use ConfigureTrait {
19
        configure as protected loadConfiguration;
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
     * @throws Anax\Configure\Exception when template file is missing
43
     *
44
     * @return string as path to the template file
45
     */
46
    public function configure($what)
47
    {
48
        $this->loadConfiguration($what);
49
50
        $includes = $this->getConfig("routeFiles", []);
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a string|null.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
51
        foreach ($includes as $include) {
52
            $this->load($include);
53
        }
54
    }
55
56
57
58
    /**
59
     * Handle the routes and match them towards the request, dispatch them
60
     * when a match is made. Each route handler may throw exceptions that
61
     * may redirect to an internal route for error handling.
62
     * Several routes can match and if the routehandler does not break
63
     * execution flow, the route matching will carry on.
64
     * Only the last routehandler will get its return value returned further.
65
     *
66
     * @param string $path    the path to find a matching handler for.
67
     * @param string $method  the request method to match.
68
     *
69
     * @return mixed content returned from route.
70
     */
71
    public function handle($path, $method = null)
72
    {
73
        try {
74
            $match = false;
75
            foreach ($this->routes as $route) {
76 View Code Duplication
                if ($route->match($path, $method)) {
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...
77
                    $this->lastRoute = $route->getRule();
78
                    $match = true;
79
                    $results = $route->handle($this->di);
80
                }
81
            }
82
83
            if ($match) {
84
                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...
85
            }
86
87
            $this->handleInternal("404");
88
        } catch (ForbiddenException $e) {
89
            $this->handleInternal("403");
90
        } catch (NotFoundException $e) {
91
            $this->handleInternal("404");
92
        } catch (InternalErrorException $e) {
93
            $this->handleInternal("500");
94
        }
95
    }
96
97
98
99
    /**
100
     * Handle an internal route, the internal routes are not exposed to the
101
     * end user.
102
     *
103
     * @param string $rule for this route.
104
     *
105
     * @return void
106
     *
107
     * @throws \Anax\Route\NotFoundException
108
     */
109
    public function handleInternal($rule)
110
    {
111
        if (!isset($this->internalRoutes[$rule])) {
112
            throw new NotFoundException("No internal route to handle: " . $rule);
113
        }
114
        $route = $this->internalRoutes[$rule];
115
        $this->lastRoute = $rule;
116
        $route->handle();
117
    }
118
119
120
121
    /**
122
     * Load routes from a config file, the file should return an array with
123
     * details of the routes. These details are used to create the routes.
124
     *
125
     * @param array $route details on the route.
126
     *
127
     * @return self
128
     */
129
    public function load($route)
130
    {
131
        $mount = isset($route["mount"]) ? rtrim($route["mount"], "/") : null;
132
        $file = $route["file"];
133
134
        $config = require($file);
135
        foreach ($config["routes"] as $route) {
136
            $path = isset($mount)
137
                ? $mount . "/" . $route["path"]
138
                : $route["path"];
139
140
            if (isset($route["internal"]) && $route["internal"]) {
141
                $this->addInternal($path, $route["callable"]);
142
                continue;
143
            }
144
145
            $this->any(
146
                $route["requestMethod"],
147
                $path,
148
                $route["callable"],
149
                $route["info"]
150
            );
151
        }
152
        return $this;
153
    }
154
155
156
157
    /**
158
     * Add a route with a request method, a path rule to match and an action
159
     * as the callback. Adding several path rules (array) results in several
160
     * routes being created.
161
     *
162
     * @param null|string|array    $method as a valid request method.
163
     * @param null|string|array    $rule   path rule for this route.
164
     * @param null|string|callable $action to implement a handler for the route.
165
     * @param null|string          $info   about the route.
166
     *
167
     * @return class|array as new route(s), class if one added, else array.
168
     */
169
    public function any($method, $rule, $action, $info = null)
170
    {
171
        $rules = is_array($rule) ? $rule : [$rule];
172
173
        $routes = [];
174
        foreach ($rules as $val) {
175
            $route = new Route();
176
            $route->set($val, $action, $method, $info);
177
            $routes[] = $route;
178
            $this->routes[] = $route;
179
        }
180
181
        return count($routes) === 1 ? $routes[0] : $routes;
182
    }
183
184
185
186
    /**
187
     * Add a route to the router by rule(s) and a callback.
188
     *
189
     * @param null|string|array    $rule   for this route.
190
     * @param null|string|callable $action a callback handler for the route.
191
     *
192
     * @return class|array as new route(s), class if one added, else array.
193
     */
194
    public function add($rule, $action = null)
195
    {
196
        return $this->any(null, $rule, $action);
197
    }
198
199
200
201
    /**
202
    * Add a default route which will be applied for any path.
203
     *
204
     * @param string|callable $action a callback handler for the route.
205
     *
206
     * @return class as new route.
207
     */
208
    public function always($action)
209
    {
210
        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 210 which is incompatible with the return type documented by Anax\Route\Router::always of type Anax\Route\class.
Loading history...
211
    }
212
213
214
215
    /**
216
     * Add a default route which will be applied for any path, if the choosen
217
     * request method is matching.
218
     *
219
     * @param null|string|array    $method as request methods
220
     * @param null|string|callable $action a callback handler for the route.
221
     *
222
     * @return class|array as new route(s), class if one added, else array.
223
     */
224
    public function all($method, $action)
225
    {
226
        return $this->any($method, null, $action);
227
    }
228
229
230
231
    /**
232
     * Shortcut to add a GET route.
233
     *
234
     * @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...
235
     * @param null|string|callable $action a callback handler for the route.
236
     *
237
     * @return class|array as new route(s), class if one added, else array.
238
     */
239
    public function get($rule, $action)
240
    {
241
        return $this->any(["GET"], $rule, $action);
242
    }
243
244
245
246
    /**
247
    * Shortcut to add a POST route.
248
     *
249
     * @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...
250
     * @param null|string|callable $action a callback handler for the route.
251
     *
252
     * @return class|array as new route(s), class if one added, else array.
253
     */
254
    public function post($rule, $action)
255
    {
256
        return $this->any(["POST"], $rule, $action);
257
    }
258
259
260
261
    /**
262
    * Shortcut to add a PUT route.
263
     *
264
     * @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...
265
     * @param null|string|callable $action a callback handler for the route.
266
     *
267
     * @return class|array as new route(s), class if one added, else array.
268
     */
269
    public function put($rule, $action)
270
    {
271
        return $this->any(["PUT"], $rule, $action);
272
    }
273
274
275
276
    /**
277
    * Shortcut to add a DELETE route.
278
     *
279
     * @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...
280
     * @param null|string|callable $action a callback handler for the route.
281
     *
282
     * @return class|array as new route(s), class if one added, else array.
283
     */
284
    public function delete($rule, $action)
285
    {
286
        return $this->any(["DELETE"], $rule, $action);
287
    }
288
289
290
291
    /**
292
     * Add an internal route to the router, this route is not exposed to the
293
     * browser and the end user.
294
     *
295
     * @param string               $rule   for this route
296
     * @param null|string|callable $action a callback handler for the route.
297
     *
298
     * @return class|array as new route(s), class if one added, else array.
299
     */
300
    public function addInternal($rule, $action)
301
    {
302
        $route = new Route();
303
        $route->set($rule, $action);
304
        $this->internalRoutes[$rule] = $route;
305
        return $route;
306
    }
307
308
309
310
    /**
311
     * Get the route for the last route that was handled.
312
     *
313
     * @return mixed
314
     */
315
    public function getLastRoute()
316
    {
317
        return $this->lastRoute;
318
    }
319
320
321
322
    /**
323
     * Get all routes.
324
     *
325
     * @return array with all routes.
326
     */
327
    public function getAll()
328
    {
329
        return $this->routes;
330
    }
331
332
333
334
    /**
335
     * Get all internal routes.
336
     *
337
     * @return array with internal routes.
338
     */
339
    public function getInternal()
340
    {
341
        return $this->internalRoutes;
342
    }
343
}
344