Test Failed
Push — master ( 976b6b...f8fb73 )
by Mikael
02:00
created

Router::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 2
1
<?php
2
3
namespace Anax\Route;
4
5
use Anax\Commons\ContainerInjectableInterface;
6
use Anax\Commons\ContainerInjectableTrait;
7
use Anax\Route\Exception\ConfigurationException;
8
use Anax\Route\Exception\ForbiddenException;
9
use Anax\Route\Exception\InternalErrorException;
10
use Anax\Route\Exception\NotFoundException;
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 Route  $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
    public function setMode($mode) : object
56
    {
57
        $this->mode = $mode;
58
        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
    public function addRoutes(array $routes) : object
86
    {
87
        if (!(isset($routes["routes"]) && is_array($routes["routes"]))) {
88
            throw new ConfigurationException("No routes found, missing key 'routes' in configuration array.");
89
        }
90
91
        foreach ($routes["routes"] as $route) {
92
            if ($route["internal"] ?? false) {
93
                $this->addInternalRoute(
94
                    $route["path"],
95
                    $route["handler"] ?? null,
96
                    $route["info"] ?? null
97
                );
98
                continue;
99
            }
100
101
            if (!(is_array($route) && array_key_exists("path", $route))) {
102
                throw new ConfigurationException("Creating route but path is not defined for route.");
103
            }
104
105
            $mount = $this->createMountPath(
106
                $routes["mount"] ?? null,
107
                $route["mount"] ?? null
108
            );
109
110
            $this->addRoute(
111
                $route["method"] ?? null,
112
                $mount,
113
                $route["path"],
114
                $route["handler"] ?? null,
115
                $route["info"] ?? null
116
            );
117
        }
118
119
        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
    public function createMountPath(
135
        string $mount1 = null,
136
        string $mount2 = null
137
    ) {
138
        $mount = null;
139
        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
            $mount = $mount1;
141
        }
142
143
        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
            $mount = $mount2;
145
        }
146
147
        trim($mount);
148
        rtrim($mount, "/");
149
        $mount = empty($mount) ? null : $mount;
150
151
        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
    public function addRoute(
171
        $method,
172
        $mount = null,
173
        $path = null,
174
        $handler = null,
175
        string $info = null
176
    ) : void {
177
        if (!is_array($path)) {
178
            $path = [$path];
179
        }
180
181
        foreach ($path as $thePath) {
182
            $route = new Route();
183
            $route->set($method, $mount, $thePath, $handler, $info);
184
            $this->routes[] = $route;
185
        }
186
    }
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
    public function addInternalRoute(
201
        string $path,
202
        $handler,
203
        string $info = null
204
    ) : void {
205
        $route = new Route();
206
        $route->set(null, null, $path, $handler, $info);
207
        $this->internalRoutes[$path] = $route;
208
    }
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
    public function handle($path, $method = null)
226
    {
227
        try {
228
            $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
            foreach ($this->routes as $route) {
230
                if ($route->match($path, $method)) {
231
                    $this->lastRoute = $route;
232
                    $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
                    $results = $route->handle($path, $this->di);
234
                    if ($results) {
235
                        return $results;
236
                    }
237
                }
238
            }
239
240
            return $this->handleInternal("404");
241
        } catch (ForbiddenException $e) {
242
            return $this->handleInternal("403");
243
        } catch (NotFoundException $e) {
244
            return $this->handleInternal("404");
245
        } catch (InternalErrorException $e) {
246
            return $this->handleInternal("500");
247
        } catch (\Exception $e) {
248
            if ($this->mode === Router::DEVELOPMENT) {
249
                throw $e;
250
            }
251
            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 $path for this route.
262
     *
263
     * @throws \Anax\Route\Exception\NotFoundException
264
     *
265
     * @return void
266
     */
267
    public function handleInternal($path)
268
    {
269
        if (!isset($this->internalRoutes[$path])) {
270
            throw new NotFoundException("No internal route to handle: " . $path);
271
        }
272
        $route = $this->internalRoutes[$path];
273
        $this->lastRoute = $route;
274
        return $route->handle(null, $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
    public function addController($method = null, $mount = null, $handler = null, $info = null)
290
    {
291
        $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
    }
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
    public function any($method = null, $path = null, $handler = null, $info = null)
307
    {
308
        $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
    }
310
311
312
313
    /**
314
     * Add a route to the router by its path(s) and a callback for any
315
     * request method .
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
    public function add($path = null, $handler = null, $info = null)
324
    {
325
        $this->addRoute(null, null, $path, $handler, $info);
326
    }
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
    public function always($handler, $info = null)
340
    {
341
        $this->addRoute(null, null, null, $handler, $info);
342
    }
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
    public function all($method, $handler, $info = null)
357
    {
358
        $this->addRoute($method, null, null, $handler, $info);
359
    }
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
    public function get($path, $handler, $info = null)
373
    {
374
        $this->addRoute(["GET"], null, $path, $handler, $info);
375
    }
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
    public function post($path, $handler, $info = null)
389
    {
390
        $this->addRoute(["POST"], null, $path, $handler, $info);
391
    }
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
    public function put($path, $handler, $info = null)
405
    {
406
        $this->addRoute(["PUT"], null, $path, $handler, $info);
407
    }
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
    public function patch($path, $handler, $info = null)
421
    {
422
        $this->addRoute(["PATCH"], null, $path, $handler, $info);
423
    }
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
    public function delete($path, $handler, $info = null)
437
    {
438
        $this->addRoute(["DELETE"], null, $path, $handler, $info);
439
    }
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
    public function options($path, $handler, $info = null)
453
    {
454
        $this->addRoute(["OPTIONS"], null, $path, $handler, $info);
455
    }
456
457
458
459
    /**
460
     * Get the route for the last route that was handled.
461
     *
462
     * @return mixed
463
     */
464
    public function getLastRoute()
465
    {
466
        return $this->lastRoute->getAbsolutePath();
467
    }
468
469
470
471
    /**
472
     * Get the route for the last route that was handled.
473
     *
474
     * @return mixed
475
     */
476
    public function getMatchedPath()
477
    {
478
        return $this->lastRoute->getMatchedPath();
479
    }
480
481
482
483
    /**
484
     * Get all routes.
485
     *
486
     * @return array with all routes.
487
     */
488
    public function getAll()
489
    {
490
        return $this->routes;
491
    }
492
493
494
495
    /**
496
     * Get all internal routes.
497
     *
498
     * @return array with internal routes.
499
     */
500
    public function getInternal()
501
    {
502
        return $this->internalRoutes;
503
    }
504
}
505