Completed
Push — master ( c7eb4e...929104 )
by Mikael
03:07
created

Router::handle()   D

Complexity

Conditions 9
Paths 28

Size

Total Lines 29
Code Lines 21

Duplication

Lines 10
Ratio 34.48 %

Code Coverage

Tests 11
CRAP Score 17.7468

Importance

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