Test Failed
Push — master ( 51dcc6...eb15c8 )
by Mikael
04:43
created

Router::addInternalRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 5
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
namespace Anax\Route;
4
5
use Anax\DI\InjectionAwareInterface;
6
use Anax\DI\InjectionAwareTrait;
7
use Anax\Route\Exception\ForbiddenException;
8
use Anax\Route\Exception\NotFoundException;
9
use Anax\Route\Exception\InternalErrorException;
10
use Anax\Route\Exception\ConfigurationException;
11
12
/**
13
 * A container for routes.
14
 */
15
class Router implements
16
    InjectionAwareInterface
17
{
18
    use InjectionAwareTrait;
19
20
21
22
    /**
23
     * @var array       $routes         all the routes.
24
     * @var array       $internalRoutes all internal routes.
25
     * @var null|string $lastRoute      last route that was matched and called.
26
     */
27
    private $routes         = [];
28
    private $internalRoutes = [];
29
    private $lastRoute      = null;
30
31
32
33
    /**
34
     * @const DEVELOPMENT Verbose with exceptions.
35
     * @const PRODUCTION  Exceptions turns into 500.
36
     */
37
    const DEVELOPMENT = 0;
38
    const PRODUCTION  = 1;
39
40
41
42
    /**
43
     * @var integer $mode current mode.
44
     */
45
    private $mode = self::DEVELOPMENT;
46
47
48
49
    /**
50
     * Set Router::DEVELOPMENT or Router::PRODUCTION mode.
51
     *
52
     * @param integer $mode which mode to set.
53
     *
54
     * @return self to enable chaining.
55
     */
56
    public function setMode(integer $mode) : object
57
    {
58
        $this->mode = $mode;
0 ignored issues
show
Documentation Bug introduced by
It seems like $mode of type object<Anax\Route\integer> is incompatible with the declared type integer of property $mode.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
59
        return $this;
60
    }
61
62
63
64
    /**
65
     * Add routes from an array where the array looks like this:
66
     * [
67
     *      "mount" => null|string, // Where to mount the routes
68
     *      "routes" => [           // All routes in this array
69
     *          [
70
     *              "info" => "Just say hi.",
71
     *              "method" => null,
72
     *              "path" => "hi",
73
     *              "handler" => function () {
74
     *                  return "Hi.";
75
     *              },
76
     *          ]
77
     *      ]
78
     * ]
79
     *
80
     * @throws ConfigurationException
81
     *
82
     * @param array $routes containing the routes to add.
83
     *
84
     * @return self to enable chaining.
85
     */
86
    public function addRoutes(array $routes) : object
87
    {
88
        $mount = null;
89
        if (isset($routes["mount"])) {
90
            $mount = rtrim($routes["mount"], "/");
91
            if (!empty($mount)) {
92
                $mount .= "/";
93
            }
94
        }
95
96
        if (!(isset($routes["routes"]) && is_array($routes["routes"]))) {
97
            throw new ConfigurationException(t("No routes found, missing key 'routes' in configuration array."));
98
        }
99
100
        foreach ($routes["routes"] as $route) {
101
            if ($route["internal"] ?? false) {
102
                $this->addInternalRoute(
103
                    $route["path"],
104
                    $route["handler"] ?? null
105
                );
106
                continue;
107
            }
108
109
            if (!array_key_exists("path", $route)) {
110
                throw new ConfigurationException(t("Creating route but path is not defined for route."));
111
            }
112
113
            $this->addRoute(
114
                $route["method"] ?? null,
115
                $mount . $route["path"],
116
                $route["handler"] ?? null,
117
                $route["info"] ?? null
118
            );
119
        }
120
121
        return $this;
122
    }
123
124
125
126
    /**
127
     * Add a route with a request method, a path rule to match and an action
128
     * as the callback. Adding several path rules (array) results in several
129
     * routes being created.
130
     *
131
     * @param string|array           $method  as request method to support
132
     * @param string                 $path    for this route
133
     * @param string|array|callable  $handler for this path, callable or equal
134
     * @param string                 $info    description of the route
135
     *
136
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
137
     */
138
    protected function addRoute(
139
        $method,
140
        string $path = null,
141
        $handler = null,
142
        string $info = null
143
    ) : void
144
    {
145
        $route = new Route();
146
        $route->set($method, $path, $handler, $info);
147
        $this->routes[] = $route;
148
    }
149
150
151
152
    /**
153
     * Add an internal route to the router, this route is not exposed to the
154
     * browser and the end user.
155
     *
156
     * @param string                 $path    for this route
157
     * @param string|array|callable  $handler for this path, callable or equal
158
     *
159
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
160
     */
161
    public function addInternalRoute(string $path, $handler) : void
162
    {
163
        $route = new Route();
164
        $route->set($path, $handler);
0 ignored issues
show
Documentation introduced by
$handler is of type callable, 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...
165
        $this->internalRoutes[$path] = $route;
166
    }
167
168
169
170
    /**
171
     * Load route from an array contining route details.
172
     *
173
     * @throws ConfigurationException
174
     *
175
     * @param array $route details on the route.
176
     *
177
     * @return self
178
     *
179
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
180
     */
181
    public function load(array $route) : object
182
    {
183
        var_dump($route);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($route); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
184
        
185
        $mount = isset($route["mount"]) ? rtrim($route["mount"], "/") : null;
186
187
        $config = $route;
188
189
        // Include the config file and load its routes
190
        $config = require($file);
0 ignored issues
show
Bug introduced by
The variable $file does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
191
        $routes = isset($config["routes"]) ? $config["routes"] : [];
192
        foreach ($routes as $route) {
193
            $path = isset($mount)
194
                ? $mount . "/" . $route["path"]
195
                : $route["path"];
196
197
            if (isset($route["internal"]) && $route["internal"]) {
198
                $this->addInternal($path, $route["callable"]);
0 ignored issues
show
Bug introduced by
The method addInternal() does not exist on Anax\Route\Router. Did you maybe mean addInternalRoute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
199
                continue;
200
            }
201
202
            $this->any(
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
203
                $route["requestMethod"],
204
                $path,
205
                $route["callable"],
206
                $route["info"]
207
            );
208
        }
209
210
        return $this;
211
    }
212
213
214
215
    /**
216
     * Load and apply configurations.
217
     *
218
     * @param array|string $what is an array with key/value config options
219
     *                           or a file to be included which returns such
220
     *                           an array.
221
     *
222
     * @return self
223
     */
224
    public function configure($what)
225
    {
226
        $this->configure2($what);
0 ignored issues
show
Bug introduced by
The method configure2() does not exist on Anax\Route\Router. Did you maybe mean configure()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
227
        $includes = $this->getConfig("routeFiles", []);
0 ignored issues
show
Bug introduced by
The method getConfig() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
228
        $items    = $this->getConfig("items", []);
0 ignored issues
show
Bug introduced by
The method getConfig() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
229
        $config = array_merge($includes, $items);
230
231
        // Add a sort field if missing, to maintain order
232
        // when sorting
233
        $sort = 1;
234
        array_walk($config, function (&$item) use (&$sort) {
235
            $item["sort"] = (isset($item["sort"]))
236
                ? $item["sort"]
237
                : $sort++;
238
        });
239
        uasort($config, function ($item1, $item2) {
240
            if ($item1["sort"] === $item2["sort"]) {
241
                return 0;
242
            }
243
            return ($item1["sort"] < $item2["sort"]) ? -1 : 1;
244
        });
245
246
        foreach ($config as $route) {
247
            $this->load($route);
248
        }
249
250
        return $this;
251
    }
252
253
254
255
    /**
256
     * Handle the routes and match them towards the request, dispatch them
257
     * when a match is made. Each route handler may throw exceptions that
258
     * may redirect to an internal route for error handling.
259
     * Several routes can match and if the routehandler does not break
260
     * execution flow, the route matching will carry on.
261
     * Only the last routehandler will get its return value returned further.
262
     *
263
     * @param string $path    the path to find a matching handler for.
264
     * @param string $method  the request method to match.
265
     *
266
     * @return mixed content returned from route.
267
     */
268
    public function handle($path, $method = null)
269
    {
270
        try {
271
            $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...
272
            foreach ($this->routes as $route) {
273
                if ($route->match($path, $method)) {
274
                    $this->lastRoute = $route->getRule();
275
                    $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...
276
                    $results = $route->handle($this->di);
277
                    if ($results) {
278
                        return $results;
279
                    }
280
                }
281
            }
282
283
            $this->handleInternal("404");
284
        } catch (ForbiddenException $e) {
285
            $this->handleInternal("403");
286
        } catch (NotFoundException $e) {
287
            $this->handleInternal("404");
288
        } catch (InternalErrorException $e) {
289
            $this->handleInternal("500");
290
        } catch (ConfigurationException $e) {
291
            if ($this->mode === Router::PRODUCTION) {
292
                $this->handleInternal("500");
293
            }
294
            throw $e;
295
        }
296
    }
297
298
299
300
    /**
301
     * Handle an internal route, the internal routes are not exposed to the
302
     * end user.
303
     *
304
     * @param string $rule for this route.
305
     *
306
     * @throws \Anax\Route\Exception\NotFoundException
307
     *
308
     * @return void
309
     */
310
    public function handleInternal($rule)
311
    {
312
        if (!isset($this->internalRoutes[$rule])) {
313
            throw new NotFoundException("No internal route to handle: " . $rule);
314
        }
315
        $route = $this->internalRoutes[$rule];
316
        $this->lastRoute = $rule;
317
        $route->handle($this->di);
318
    }
319
320
321
322
    /**
323
     * Add a route to the router by rule(s) and a callback.
324
     *
325
     * @param null|string|array    $rule   for this route.
326
     * @param null|string|callable $action a callback handler for the route.
327
     *
328
     * @return class|array as new route(s), class if one added, else array.
329
     */
330 6
    public function add($rule, $action = null)
331
    {
332 6
        return $this->any(null, $rule, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
333
    }
334
335
336
337
    /**
338
    * Add a default route which will be applied for any path.
339
     *
340
     * @param string|callable $action a callback handler for the route.
341
     *
342
     * @return class as new route.
343
     */
344 1
    public function always($action)
345
    {
346 1
        return $this->any(null, null, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
347
    }
348
349
350
351
    /**
352
     * Add a default route which will be applied for any path, if the choosen
353
     * request method is matching.
354
     *
355
     * @param null|string|array    $method as request methods
356
     * @param null|string|callable $action a callback handler for the route.
357
     *
358
     * @return class|array as new route(s), class if one added, else array.
359
     */
360 1
    public function all($method, $action)
361
    {
362 1
        return $this->any($method, null, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
363
    }
364
365
366
367
    /**
368
     * Shortcut to add a GET route.
369
     *
370
     * @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...
371
     * @param null|string|callable $action a callback handler for the route.
372
     *
373
     * @return class|array as new route(s), class if one added, else array.
374
     */
375
    public function get($rule, $action)
376
    {
377
        return $this->any(["GET"], $rule, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
378
    }
379
380
381
382
    /**
383
    * Shortcut to add a POST route.
384
     *
385
     * @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...
386
     * @param null|string|callable $action a callback handler for the route.
387
     *
388
     * @return class|array as new route(s), class if one added, else array.
389
     */
390
    public function post($rule, $action)
391
    {
392
        return $this->any(["POST"], $rule, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
393
    }
394
395
396
397
    /**
398
    * Shortcut to add a PUT route.
399
     *
400
     * @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...
401
     * @param null|string|callable $action a callback handler for the route.
402
     *
403
     * @return class|array as new route(s), class if one added, else array.
404
     */
405
    public function put($rule, $action)
406
    {
407
        return $this->any(["PUT"], $rule, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
408
    }
409
410
411
412
    /**
413
    * Shortcut to add a DELETE route.
414
     *
415
     * @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...
416
     * @param null|string|callable $action a callback handler for the route.
417
     *
418
     * @return class|array as new route(s), class if one added, else array.
419
     */
420
    public function delete($rule, $action)
421
    {
422
        return $this->any(["DELETE"], $rule, $action);
0 ignored issues
show
Bug introduced by
The method any() does not seem to exist on object<Anax\Route\Router>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
423
    }
424
425
426
427
    /**
428
     * Get the route for the last route that was handled.
429
     *
430
     * @return mixed
431
     */
432
    public function getLastRoute()
433
    {
434
        return $this->lastRoute;
435
    }
436
437
438
439
    /**
440
     * Get all routes.
441
     *
442
     * @return array with all routes.
443
     */
444
    public function getAll()
445
    {
446
        return $this->routes;
447
    }
448
449
450
451
    /**
452
     * Get all internal routes.
453
     *
454
     * @return array with internal routes.
455
     */
456
    public function getInternal()
457
    {
458
        return $this->internalRoutes;
459
    }
460
}
461