Passed
Push — master ( 9b8457...f05bc0 )
by Alexander
01:49
created

Router::match()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 4
nop 2
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l;
4
5
use Closure;
6
7
/**
8
 * Class Router
9
 *
10
 * @package alkemann\h2l
11
 */
12
class Router
13
{
14
    public static $DELIMITER = '|';
15
16
    private static $aliases = [];
17
    private static $routes = [];
18
19
    /**
20
     * @param string $alias
21
     * @param string $real
22
     */
23
    public static function alias(string $alias, string $real): void
24
    {
25
        self::$aliases[$alias] = $real;
26
    }
27
28
    /**
29
     * Add new dynamic route to application
30
     *
31
     * @param string $url Regex that is valid for preg_match, including named groups
32
     * @param callable $callable
33
     * @param mixed $methods a single Request::<GET/POST/PUT/PATCH/DELETE> or an array of multiple
34
     * @internal param Closure $closure Code to run on this match
35
     */
36
    public static function add(string $url, callable $callable, $methods = [Request::GET]): void
37
    {
38
        if ($callable instanceof Closure) {
39
            $closure = $callable;
40
        } else {
41
            $closure = Closure::fromCallable($callable);
42
        }
43
        foreach ((array)$methods as $method) {
44
            self::$routes[$method][$url] = $closure;
45
        }
46
    }
47
48
    /**
49
     * Given a request url and request method, identify dynamic route or return fixed route
50
     *
51
     * @param string $url Request url, i.e. '/api/user/32'
52
     * @param string $method Request::<GET/POST/PATCH/PUT/DELETE>
53
     * @return interfaces\Route
54
     */
55
    public static function match(string $url, string $method = Request::GET): interfaces\Route
56
    {
57
        $url = self::$aliases[$url] ?? $url;
58
59
        if (isset(self::$routes[$method])) {
60
            if (isset(self::$routes[$method][$url])) {
61
                return new Route($url, self::$routes[$method][$url]);
62
            }
63
64
            // TODO cache of previous matched dynamic routes
65
            $route = self::matchDynamicRoute($url, $method);
66
            if ($route) {
67
                return $route;
68
            }
69
        }
70
71
        // TODO cache of valid static routes, maybe with a try, catch, finally?
72
        return new Route($url, function (Request $request) {
73
            $page = response\Page::fromRequest($request);
74
            if ($page->isValid()) {
75
                return $page;
76
            }
77
        });
78
    }
79
80
    private static function matchDynamicRoute(string $url, string $method = Request::GET): ?interfaces\Route
81
    {
82
        foreach (self::$routes[$method] as $route => $cb) {
83
            if ($route[0] !== substr($route, -1) || $route[0] !== static::$DELIMITER) {
84
                continue;
85
            }
86
            $result = preg_match($route, $url, $matches);
87
            if (!$result) {
88
                continue;
89
            }
90
91
            $parameters = array_filter(
92
                $matches,
93
                function ($v) {
94
                    return !is_int($v);
95
                },
96
                \ARRAY_FILTER_USE_KEY
0 ignored issues
show
Bug introduced by
ARRAY_FILTER_USE_KEY of type integer is incompatible with the type integer expected by parameter $flag of array_filter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
                /** @scrutinizer ignore-type */ \ARRAY_FILTER_USE_KEY
Loading history...
97
            );
98
99
            return new Route($url, $cb, $parameters);
100
        }
101
102
        return null;
103
    }
104
}
105