Passed
Push — master ( 1d6a03...c7eb4e )
by Mikael
01:34
created

Router::any()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 14
c 1
b 0
f 0
ccs 10
cts 10
cp 1
rs 9.2
cc 4
eloc 9
nc 8
nop 4
crap 4
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;
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;
109 6
                    $results = $route->handle($this->di);
110 6
                    if ($results) {
111 3
                        return $results;
112
                    }
113 4
                }
114 5
            }
115
116 3
            if ($match) {
117 3
                return;
118
            }
119
120 2
            $this->handleInternal("404");
121 2
        } catch (ForbiddenException $e) {
122
            $this->handleInternal("403");
123
        } catch (NotFoundException $e) {
124
            $this->handleInternal("404");
125
        } catch (InternalErrorException $e) {
126
            $this->handleInternal("500");
127
        } catch (ConfigurationException $e) {
128
            if ($this->getConfig("mode", Router::DEVELOPMENT) === Router::PRODUCTION) {
129
                $this->handleInternal("500");
130
            }
131
            throw $e;
132
        }
133 2
    }
134
135
136
137
    /**
138
     * Handle an internal route, the internal routes are not exposed to the
139
     * end user.
140
     *
141
     * @param string $rule for this route.
142
     *
143
     * @throws \Anax\Route\Exception\NotFoundException
144
     *
145
     * @return void
146
     */
147 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...
148
    {
149 2
        if (!isset($this->internalRoutes[$rule])) {
150
            throw new NotFoundException("No internal route to handle: " . $rule);
151
        }
152 2
        $route = $this->internalRoutes[$rule];
153 2
        $this->lastRoute = $rule;
154 2
        $route->handle($this->di);
155 2
    }
156
157
158
159
    /**
160
     * Load routes from a config file, the file should return an array with
161
     * details of the routes. These details are used to create the routes.
162
     *
163
     * @param array $route details on the route.
164
     *
165
     * @return self
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
            $config = require($file);
181
        }
182
183
        $routes = isset($config["routes"]) ? $config["routes"] : [];
184
        foreach ($routes as $route) {
185
            $path = isset($mount)
186
                ? $mount . "/" . $route["path"]
187
                : $route["path"];
188
189
            if (isset($route["internal"]) && $route["internal"]) {
190
                $this->addInternal($path, $route["callable"]);
191
                continue;
192
            }
193
194
            $this->any(
195
                $route["requestMethod"],
196
                $path,
197
                $route["callable"],
198
                $route["info"]
199
            );
200
        }
201
202
        return $this;
203
    }
204
205
206
207
    /**
208
     * Add a route with a request method, a path rule to match and an action
209
     * as the callback. Adding several path rules (array) results in several
210
     * routes being created.
211
     *
212
     * @param null|string|array    $method as a valid request method.
213
     * @param null|string|array    $rule   path rule for this route.
214
     * @param null|string|callable $action to implement a handler for the route.
215
     * @param null|string          $info   about the route.
216
     *
217
     * @return class|array as new route(s), class if one added, else array.
218
     */
219 6
    public function any($method, $rule, $action, $info = null)
220
    {
221 6
        $rules = is_array($rule) ? $rule : [$rule];
222
223 6
        $routes = [];
224 6
        foreach ($rules as $val) {
225 6
            $route = new Route();
226 6
            $route->set($val, $action, $method, $info);
227 6
            $routes[] = $route;
228 6
            $this->routes[] = $route;
229 6
        }
230
231 6
        return count($routes) === 1 ? $routes[0] : $routes;
232
    }
233
234
235
236
    /**
237
     * Add a route to the router by rule(s) and a callback.
238
     *
239
     * @param null|string|array    $rule   for this route.
240
     * @param null|string|callable $action a callback handler for the route.
241
     *
242
     * @return class|array as new route(s), class if one added, else array.
243
     */
244 5
    public function add($rule, $action = null)
245
    {
246 5
        return $this->any(null, $rule, $action);
247
    }
248
249
250
251
    /**
252
    * Add a default route which will be applied for any path.
253
     *
254
     * @param string|callable $action a callback handler for the route.
255
     *
256
     * @return class as new route.
257
     */
258
    public function always($action)
259
    {
260
        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 260 which is incompatible with the return type documented by Anax\Route\Router::always of type Anax\Route\class.
Loading history...
261
    }
262
263
264
265
    /**
266
     * Add a default route which will be applied for any path, if the choosen
267
     * request method is matching.
268
     *
269
     * @param null|string|array    $method as request methods
270
     * @param null|string|callable $action a callback handler for the route.
271
     *
272
     * @return class|array as new route(s), class if one added, else array.
273
     */
274 1
    public function all($method, $action)
275
    {
276 1
        return $this->any($method, null, $action);
277
    }
278
279
280
281
    /**
282
     * Shortcut to add a GET route.
283
     *
284
     * @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...
285
     * @param null|string|callable $action a callback handler for the route.
286
     *
287
     * @return class|array as new route(s), class if one added, else array.
288
     */
289
    public function get($rule, $action)
290
    {
291
        return $this->any(["GET"], $rule, $action);
292
    }
293
294
295
296
    /**
297
    * Shortcut to add a POST route.
298
     *
299
     * @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...
300
     * @param null|string|callable $action a callback handler for the route.
301
     *
302
     * @return class|array as new route(s), class if one added, else array.
303
     */
304
    public function post($rule, $action)
305
    {
306
        return $this->any(["POST"], $rule, $action);
307
    }
308
309
310
311
    /**
312
    * Shortcut to add a PUT route.
313
     *
314
     * @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...
315
     * @param null|string|callable $action a callback handler for the route.
316
     *
317
     * @return class|array as new route(s), class if one added, else array.
318
     */
319
    public function put($rule, $action)
320
    {
321
        return $this->any(["PUT"], $rule, $action);
322
    }
323
324
325
326
    /**
327
    * Shortcut to add a DELETE route.
328
     *
329
     * @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...
330
     * @param null|string|callable $action a callback handler for the route.
331
     *
332
     * @return class|array as new route(s), class if one added, else array.
333
     */
334
    public function delete($rule, $action)
335
    {
336
        return $this->any(["DELETE"], $rule, $action);
337
    }
338
339
340
341
    /**
342
     * Add an internal route to the router, this route is not exposed to the
343
     * browser and the end user.
344
     *
345
     * @param string               $rule   for this route
346
     * @param null|string|callable $action a callback handler for the route.
347
     *
348
     * @return class|array as new route(s), class if one added, else array.
349
     */
350 2
    public function addInternal($rule, $action)
351
    {
352 2
        $route = new Route();
353 2
        $route->set($rule, $action);
354 2
        $this->internalRoutes[$rule] = $route;
355 2
        return $route;
356
    }
357
358
359
360
    /**
361
     * Get the route for the last route that was handled.
362
     *
363
     * @return mixed
364
     */
365
    public function getLastRoute()
366
    {
367
        return $this->lastRoute;
368
    }
369
370
371
372
    /**
373
     * Get all routes.
374
     *
375
     * @return array with all routes.
376
     */
377
    public function getAll()
378
    {
379
        return $this->routes;
380
    }
381
382
383
384
    /**
385
     * Get all internal routes.
386
     *
387
     * @return array with internal routes.
388
     */
389
    public function getInternal()
390
    {
391
        return $this->internalRoutes;
392
    }
393
}
394