Router   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 192
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 77
c 1
b 0
f 1
dl 0
loc 192
rs 9.76
wmc 33

5 Methods

Rating   Name   Duplication   Size   Complexity  
A matchMethod() 0 6 2
B matchRoute() 0 29 8
C match() 0 45 12
B compileRoute() 0 31 8
A maping() 0 11 3
1
<?php
2
3
namespace Synext\Routers;
4
5
use Synext\Exceptions\RoutersException;
6
7
class Router
8
{
9
    /**
10
     * Contain all routes
11
     *
12
     * @var array
13
     */
14
    private $routers = [];
15
16
    /**
17
     * Contain all named routes
18
     *
19
     * @var array
20
     */
21
    private $routersWithName = [];
22
23
    /**
24
     * All matched types for route parameter
25
     *
26
     * @var array
27
     */
28
    private $routeParamMatcheTypes = [
0 ignored issues
show
introduced by
The private property $routeParamMatcheTypes is not used, and could be removed.
Loading history...
29
        'i'  => '[0-9]++',
30
        'a'  => '[0-9A-Za-z]++',
31
        's' => '^[a-z0-9-]+$',
32
        'h'  => '[0-9A-Fa-f]++',
33
        '*'  => '.+?',
34
        'o'   => '[^/\.]++'
35
    ];
36
37
38
    /**
39
     * @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host)
40
     */
41
    protected $basePath = '';
42
43
    /**
44
     * Maping a route to a target
45
     *
46
     * @param string $method Accept one like GET or multiple methods with this syntax GET|POST|PATCH|PUT|DELETE
47
     * @param string $route You can set custom regex by started line with @ or use pret-set regex like {i:id}
48
     * @param mixed $target Any target available for route pointer
49
     * @param string $name Optional route name , Set it if you want to generate a route url
50
     * @throws RoutersException
51
     * @return $this
52
     */
53
54
    public function maping(string $method, string $route, $target, string $name = null)
55
    {
56
        $this->routers[] = [$method, $route, $target, $name];
57
58
        if ($name) {
59
            if (isset($this->routersWithName[$name])) {
60
                throw new RoutersException("The route named '{$name}' cannot be redeclared");
61
            }
62
            $this->routersWithName[$name] = $route;
63
        }
64
        return $this;
65
    }
66
67
    /**
68
     * Compile the regex for a given route (EXPENSIVE)
69
     * @param $route
70
     * @return string
71
     */
72
    private function compileRoute($route)
73
    {
74
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
75
            $matchTypes = $this->matchTypes;
0 ignored issues
show
Bug Best Practice introduced by
The property matchTypes does not exist on Synext\Routers\Router. Did you maybe forget to declare it?
Loading history...
76
            foreach ($matches as $match) {
77
                list($block, $pre, $type, $param, $optional) = $match;
78
79
                if (isset($matchTypes[$type])) {
80
                    $type = $matchTypes[$type];
81
                }
82
                if ($pre === '.') {
83
                    $pre = '\.';
84
                }
85
86
                $optional = $optional !== '' ? '?' : null;
87
88
                //Older versions of PCRE require the 'P' in (?P<named>)
89
                $pattern = '(?:'
90
                        . ($pre !== '' ? $pre : null)
91
                        . '('
92
                        . ($param !== '' ? "?P<$param>" : null)
93
                        . $type
94
                        . ')'
95
                        . $optional
96
                        . ')'
97
                        . $optional;
98
99
                $route = str_replace($block, $pattern, $route);
100
            }
101
        }
102
        return "`^$route$`u";
103
    }
104
105
106
    /**
107
     * Check if request method match with defind method
108
     *
109
     * @param string $method
110
     * @param string $requestMethod
111
     * @return boolean
112
     */
113
    private function matchMethod(string $method, string $requestMethod): bool
114
    {
115
        if (is_bool(strpos($method, $requestMethod))) {
0 ignored issues
show
introduced by
The condition is_bool(strpos($method, $requestMethod)) is always false.
Loading history...
116
            return false;
117
        }
118
        return true;
119
    }
120
121
    private function matchRoute(string $route, string $requestUri, string $lastrequestUriChar)
122
    {
123
124
        if ($route === '*') {
125
            // * wildcard (matches all)
126
            return true;
127
        } elseif (isset($route[0]) && $route[0] === '@') {
128
            // @ regex delimiter
129
            $pattern = '`' . substr($route, 1) . '`u';
130
            return preg_match($pattern, $requestUri, $params) === 1;
131
        } elseif (($position = strpos($route, '[')) === false) {
132
            // No params in url, do string comparison
133
            return strcmp($requestUri, $route) === 0;
134
        } else {
135
            /**
136
             * Compare longest non-param string with url before moving on to regex
137
             * Check if last character before param is a slash, because it could be optional
138
             * if param is optional too (see https://github.com/dannyvankooten/AltoRouter/issues/241)
139
             * if (strncmp($requestUri, $route, $position) !== 0 && ($lastrequestUriChar === '/' ||
140
             * $route[$position-1] !== '/')) {
141
             * continue;}
142
             * $regex = $this->compileRoute($route);
143
             * $match = preg_match($regex, $requestUri, $params) === 1;
144
             */
145
146
            if (strncmp($requestUri, $route, $position) !== 0
147
                && ($lastrequestUriChar === '/' || $route[$position-1] !== '/')) {
148
                $regex = $this->compileRoute($route);
149
                return preg_match($regex, $requestUri, $params) === 1;
150
            }
151
        }
152
    }
153
154
    public function match(string $requestUri = null, string $requestMethod = null)
155
    {
156
157
        $params = [];
158
        if (is_null($requestUri)) {
159
            $requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
160
        }
161
        // base path remove from request uri
162
        $requestUri = substr($requestUri, strlen($this->basePath));
163
164
        //set request default mehod to GET if is not defind
165
        if (is_null($requestMethod)) {
166
            $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
167
        }
168
169
        if (!is_bool(($strpos = strpos($requestUri, '?')))) {
0 ignored issues
show
introduced by
The condition is_bool($strpos = strpos($requestUri, '?')) is always false.
Loading history...
170
            $requestUri = substr($requestUri, 0, $strpos);
171
        }
172
173
        $lastrequestUriChar = $requestUri[strlen($requestUri) - 1];
174
175
        foreach ($this->routers as $handler) {
176
            list($method, $route, $target, $name) = $handler;
177
178
            if (!$this->matchMethod($method, $requestMethod)) {
179
                continue;
180
            }
181
            $matchRoute = $this->matchRoute($route, $requestUri, $lastrequestUriChar);
182
183
            if ($matchRoute) {
184
                if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
185
                    foreach ($params as $key => $value) {
186
                        if (is_numeric($key)) {
187
                            unset($params[$key]);
188
                        }
189
                    }
190
                }
191
                return [
192
                    'target' => $target,
193
                    'params' => $params,
194
                    'name' => $name
195
                ];
196
            }
197
        }
198
        return false;
199
    }
200
}
201