Passed
Branch master (11b479)
by Mikael
03:34
created

Router::createMountPath()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.6333
c 0
b 0
f 0
cc 4
nc 8
nop 2
crap 4
1
<?php
2
3
namespace Anax\Route;
4
5
use Anax\Commons\ContainerInjectableInterface;
6
use Anax\Commons\ContainerInjectableTrait;
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 router to hold and match routes.
14
 */
15
class Router implements ContainerInjectableInterface
16
{
17
    use ContainerInjectableTrait;
18
19
20
21
    /**
22
     * @var array       $routes         all the routes.
23
     * @var array       $internalRoutes all internal routes.
24
     * @var null|string $lastRoute      last route that was matched and called.
25
     */
26
    private $routes         = [];
27
    private $internalRoutes = [];
28
    private $lastRoute      = null;
29
30
31
32
    /**
33
     * @const DEVELOPMENT Verbose with exceptions.
34
     * @const PRODUCTION  Exceptions turns into 500.
35
     */
36
    const DEVELOPMENT = 0;
37
    const PRODUCTION  = 1;
38
39
40
41
    /**
42
     * @var integer $mode current mode.
43
     */
44
    private $mode = self::DEVELOPMENT;
45
46
47
48
    /**
49
     * Set Router::DEVELOPMENT or Router::PRODUCTION mode.
50
     *
51
     * @param integer $mode which mode to set.
52
     *
53
     * @return self to enable chaining.
54
     */
55 2
    public function setMode($mode) : object
56
    {
57 2
        $this->mode = $mode;
58 2
        return $this;
59
    }
60
61
62
63
    /**
64
     * Add routes from an array where the array looks like this:
65
     * [
66
     *      "mount" => null|string, // Where to mount the routes
67
     *      "routes" => [           // All routes in this array
68
     *          [
69
     *              "info" => "Just say hi.",
70
     *              "method" => null,
71
     *              "path" => "hi",
72
     *              "handler" => function () {
73
     *                  return "Hi.";
74
     *              },
75
     *          ]
76
     *      ]
77
     * ]
78
     *
79
     * @throws ConfigurationException
80
     *
81
     * @param array $routes containing the routes to add.
82
     *
83
     * @return self to enable chaining.
84
     */
85 5
    public function addRoutes(array $routes) : object
86
    {
87 5
        if (!(isset($routes["routes"]) && is_array($routes["routes"]))) {
88 2
            throw new ConfigurationException("No routes found, missing key 'routes' in configuration array.");
89
        }
90
91 3
        foreach ($routes["routes"] as $route) {
92 3
            if ($route["internal"] ?? false) {
93 1
                $this->addInternalRoute(
94 1
                    $route["path"],
95 1
                    $route["handler"] ?? null,
96 1
                    $route["info"] ?? null
97
                );
98 1
                continue;
99
            }
100
101 3
            if (!(is_array($route) && array_key_exists("path", $route))) {
102 1
                throw new ConfigurationException("Creating route but path is not defined for route.");
103
            }
104
105 2
            $mount = $this->createMountPath(
106 2
                $routes["mount"] ?? null,
107 2
                $route["mount"] ?? null
108
            );
109
110 2
            $this->addRoute(
111 2
                $route["method"] ?? null,
112 2
                $mount,
113 2
                $route["path"],
114 2
                $route["handler"] ?? null,
115 2
                $route["info"] ?? null
116
            );
117
        }
118
119 2
        return $this;
120
    }
121
122
123
124
    /**
125
     * Prepare the mount string from configuration, use $mount1 or $mount2,
126
     * the latter supersedes the first.
127
     *
128
     * @param string $mount1 first suggestion to mount path.
129
     * @param string $mount2 second suggestion to mount path, ovverides
130
     *                       the first.
131
     *
132
     * @return string|null as mount path.
133
     */
134 2
    public function createMountPath(
135
        string $mount1 = null,
136
        string $mount2 = null
137
    ) {
138 2
        $mount = null;
139 2
        if ($mount1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mount1 of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
140 2
            $mount = $mount1;
141
        }
142
143 2
        if ($mount2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mount2 of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
144 1
            $mount = $mount2;
145
        }
146
147 2
        trim($mount);
148 2
        rtrim($mount, "/");
149 2
        $mount = empty($mount) ? null : $mount;
150
151 2
        return $mount;
152
    }
153
154
155
156
    /**
157
     * Add a route with a request method, a path rule to match and an action
158
     * as the callback. Adding several path rules (array) results in several
159
     * routes being created.
160
     *
161
     * @param string|array           $method  as request method to support
162
     * @param string                 $mount   prefix to $path
163
     * @param string|array           $path    for this route, array for several
164
     *                                        paths
165
     * @param string|array|callable  $handler for this path, callable or equal
166
     * @param string                 $info    description of the route
167
     *
168
     * @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...
169
     */
170 89
    protected function addRoute(
171
        $method,
172
        $mount = null,
173
        $path = null,
174
        $handler = null,
175
        string $info = null
176
    ) : void {
177 89
        if (!is_array($path)) {
178 88
            $path = [$path];
179
        }
180
181 89
        foreach ($path as $thePath) {
182 89
            $route = new Route();
183 89
            $route->set($method, $mount, $thePath, $handler, $info);
184 89
            $this->routes[] = $route;
185
        }
186 89
    }
187
188
189
190
    /**
191
     * Add an internal route to the router, this route is not exposed to the
192
     * browser and the end user.
193
     *
194
     * @param string                 $path    for this route
195
     * @param string|array|callable  $handler for this path, callable or equal
196
     * @param string                 $info    description of the route
197
     *
198
     * @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...
199
     */
200 12
    public function addInternalRoute(
201
        string $path,
202
        $handler,
203
        string $info = null
204
    ) : void {
205 12
        $route = new Route();
206 12
        $route->set(null, null, $path, $handler, $info);
207 12
        $this->internalRoutes[$path] = $route;
208 12
    }
209
210
211
212
    /**
213
     * Handle the routes and match them towards the request, dispatch them
214
     * when a match is made. Each route handler may throw exceptions that
215
     * may redirect to an internal route for error handling.
216
     * Several routes can match and if the routehandler does not break
217
     * execution flow, the route matching will carry on.
218
     * Only the last routehandler will get its return value returned further.
219
     *
220
     * @param string $path    the path to find a matching handler for.
221
     * @param string $method  the request method to match.
222
     *
223
     * @return mixed content returned from route.
224
     */
225 89
    public function handle($path, $method = null)
226
    {
227
        try {
228 89
            $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...
229 89
            foreach ($this->routes as $route) {
230 88
                if ($route->match($path, $method)) {
231 87
                    $this->lastRoute = $route->getAbsolutePath();
232 87
                    $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...
233 87
                    $results = $route->handle($path, $this->di);
234 80
                    if ($results) {
235 81
                        return $results;
236
                    }
237
                }
238
            }
239
240 4
            return $this->handleInternal("404");
241 7
        } catch (ForbiddenException $e) {
242 2
            return $this->handleInternal("403");
243 5
        } catch (NotFoundException $e) {
244 1
            return $this->handleInternal("404");
245 4
        } catch (InternalErrorException $e) {
246 2
            return $this->handleInternal("500");
247 2
        } catch (\Exception $e) {
248 2
            if ($this->mode === Router::DEVELOPMENT) {
249 1
                throw $e;
250
            }
251 1
            return $this->handleInternal("500");
252
        }
253
    }
254
255
256
257
    /**
258
     * Handle an internal route, the internal routes are not exposed to the
259
     * end user.
260
     *
261
     * @param string $rule for this route.
262
     *
263
     * @throws \Anax\Route\Exception\NotFoundException
264
     *
265
     * @return void
266
     */
267 11
    public function handleInternal($rule)
268
    {
269 11
        if (!isset($this->internalRoutes[$rule])) {
270 1
            throw new NotFoundException("No internal route to handle: " . $rule);
271
        }
272 10
        $route = $this->internalRoutes[$rule];
273 10
        $this->lastRoute = $rule;
274 10
        return $route->handle($this->di);
275
    }
276
277
278
279
    /**
280
     * Add a route having a controller as a handler.
281
     *
282
     * @param string|array    $method  as request method to support
283
     * @param string|array    $mount   for this route.
284
     * @param string|callable $handler a callback handler for the route.
285
     * @param string          $info    description of the route
286
     *
287
     * @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...
288
     */
289 6
    public function addController($method = null, $mount = null, $handler = null, $info = null)
290
    {
291 6
        $this->addRoute($method, $mount, null, $handler, $info);
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 289 can also be of type null; however, Anax\Route\Router::addRoute() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $mount defined by parameter $mount on line 289 can also be of type array; however, Anax\Route\Router::addRoute() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
292 6
    }
293
294
295
296
    /**
297
     * Add a route to the router by its method(s),  path(s) and a callback.
298
     *
299
     * @param string|array    $method  as request method to support
300
     * @param string|array    $path    for this route.
301
     * @param string|callable $handler a callback handler for the route.
302
     * @param string          $info    description of the route
303
     *
304
     * @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...
305
     */
306 12
    public function any($method = null, $path = null, $handler = null, $info = null)
307
    {
308 12
        $this->addRoute($method, null, $path, $handler, $info);
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 306 can also be of type null; however, Anax\Route\Router::addRoute() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
309 12
    }
310
311
312
313
    /**
314
     * Add a route to the router by its path(s) and a callback for any
315
     * request method (alias for any()).
316
     *
317
     * @param string|array    $path    for this route.
318
     * @param string|callable $handler a callback handler for the route.
319
     * @param string          $info    description of the route
320
     *
321
     * @return void
322
     */
323 20
    public function add($path = null, $handler = null, $info = null)
324
    {
325 20
        $this->addRoute(null, null, $path, $handler, $info);
326 20
    }
327
328
329
330
    /**
331
    * Add a default route which will be applied for any path and any
332
    * request method.
333
     *
334
     * @param string|callable $handler a callback handler for the route.
335
     * @param string          $info    description of the route
336
     *
337
     * @return void
338
     */
339 48
    public function always($handler, $info = null)
340
    {
341 48
        $this->addRoute(null, null, null, $handler, $info);
342 48
    }
343
344
345
346
    /**
347
     * Add a default route which will be applied for any path, if the choosen
348
     * request method is matching.
349
     *
350
     * @param string|array    $method  as request method to support
351
     * @param string|callable $handler a callback handler for the route.
352
     * @param string          $info    description of the route
353
     *
354
     * @return void
355
     */
356 7
    public function all($method, $handler, $info = null)
357
    {
358 7
        $this->addRoute($method, null, null, $handler, $info);
359 7
    }
360
361
362
363
    /**
364
     * Shortcut to add a GET route for the http request method GET.
365
     *
366
     * @param string|array    $path   for this route.
367
     * @param string|callable $handler a callback handler for the route.
368
     * @param string          $info    description of the route
369
     *
370
     * @return void
371
     */
372 6
    public function get($path, $handler, $info = null)
373
    {
374 6
        $this->addRoute(["GET"], null, $path, $handler, $info);
375 6
    }
376
377
378
379
    /**
380
     * Shortcut to add a POST route for the http request method POST.
381
     *
382
     * @param string|array    $path   for this route.
383
     * @param string|callable $handler a callback handler for the route.
384
     * @param string          $info    description of the route
385
     *
386
     * @return void
387
     */
388 6
    public function post($path, $handler, $info = null)
389
    {
390 6
        $this->addRoute(["POST"], null, $path, $handler, $info);
391 6
    }
392
393
394
395
    /**
396
     * Shortcut to add a PUT route for the http request method PUT.
397
     *
398
     * @param string|array    $path   for this route.
399
     * @param string|callable $handler a callback handler for the route.
400
     * @param string          $info    description of the route
401
     *
402
     * @return void
403
     */
404 6
    public function put($path, $handler, $info = null)
405
    {
406 6
        $this->addRoute(["PUT"], null, $path, $handler, $info);
407 6
    }
408
409
410
411
    /**
412
     * Shortcut to add a PATCH route for the http request method PATCH.
413
     *
414
     * @param string|array    $path   for this route.
415
     * @param string|callable $handler a callback handler for the route.
416
     * @param string          $info    description of the route
417
     *
418
     * @return void
419
     */
420 6
    public function patch($path, $handler, $info = null)
421
    {
422 6
        $this->addRoute(["PATCH"], null, $path, $handler, $info);
423 6
    }
424
425
426
427
    /**
428
     * Shortcut to add a DELETE route for the http request method DELETE.
429
     *
430
     * @param string|array    $path   for this route.
431
     * @param string|callable $handler a callback handler for the route.
432
     * @param string          $info    description of the route
433
     *
434
     * @return void
435
     */
436 6
    public function delete($path, $handler, $info = null)
437
    {
438 6
        $this->addRoute(["DELETE"], null, $path, $handler, $info);
439 6
    }
440
441
442
443
    /**
444
     * Shortcut to add a OPTIONS route for the http request method OPTIONS.
445
     *
446
     * @param string|array    $path   for this route.
447
     * @param string|callable $handler a callback handler for the route.
448
     * @param string          $info    description of the route
449
     *
450
     * @return void
451
     */
452 6
    public function options($path, $handler, $info = null)
453
    {
454 6
        $this->addRoute(["OPTIONS"], null, $path, $handler, $info);
455 6
    }
456
457
458
459
    /**
460
     * Get the route for the last route that was handled.
461
     *
462
     * @return mixed
463
     */
464 1
    public function getLastRoute()
465
    {
466 1
        return $this->lastRoute;
467
    }
468
469
470
471
    /**
472
     * Get all routes.
473
     *
474
     * @return array with all routes.
475
     */
476 78
    public function getAll()
477
    {
478 78
        return $this->routes;
479
    }
480
481
482
483
    /**
484
     * Get all internal routes.
485
     *
486
     * @return array with internal routes.
487
     */
488 5
    public function getInternal()
489
    {
490 5
        return $this->internalRoutes;
491
    }
492
}
493