RouterParser::isValidRouteRegex()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.049

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 9
cts 10
cp 0.9
rs 8.2222
c 0
b 0
f 0
cc 7
eloc 8
nc 4
nop 5
crap 7.049
1
<?php
2
namespace HakimCh\Http;
3
4
class RouterParser implements RouterParserInterface
5
{
6
    protected $params = [];
7
    protected $matchTypes = [
8
        'i'  => '[0-9]++',
9
        'a'  => '[0-9A-Za-z]++',
10
        'h'  => '[0-9A-Fa-f]++',
11
        '*'  => '.+?',
12
        '**' => '.++',
13
        ''   => '[^/\.]++'
14
    ];
15
16
    /**
17
     * Create router in one call from config.
18
     *
19
     * @param array $matchTypes
20
     */
21 19
    public function __construct($matchTypes = [])
22
    {
23 19
        $this->setMatchTypes($matchTypes);
24 19
    }
25
26
    /**
27
     * Add named match types. It uses array_merge so keys can be overwritten.
28
     *
29
     * @param array $matchTypes The key is the name and the value is the regex.
30
     */
31 19
    public function setMatchTypes($matchTypes)
32
    {
33 19
        $this->matchTypes = array_merge($this->matchTypes, $matchTypes);
34 19
    }
35
36
    /**
37
     * Get the url from a route name
38
     *
39
     * @param string $basePath
40
     * @param string $route
41
     * @param array $params
42
     *
43
     * @return string
44
     */
45 2
    public function generateUrl($basePath, $route, array $params)
46
    {
47 2
        $url = $basePath . $route;
48
49 2
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
50 2
            foreach ($matches as $match) {
51 2
                $pre   = $match[1];
52 2
                $param = $match[3];
53 2
                $block = $pre ? substr($match[0], 1) : $match[0];
54
55 2
                if (isset($params[$param])) {
56 2
                    $url = str_replace($block, $params[$param], $url);
57 2
                } elseif ($match[4]) {
58 1
                    $url = str_replace($pre . $block, '', $url);
59 1
                }
60 2
            }
61 2
        }
62
63 2
        return $url;
64
    }
65
66
    /**
67
     * @param string $method
68
     * @param string $requestMethod
69
     * @param string $routeString
70
     * @param string $requestUrl
71
     *
72
     * @return mixed
73
     */
74 9
    public function methodMatch($method, $requestMethod, $routeString, $requestUrl)
75
    {
76 9
        $methods = explode('|', $method);
77
78 9
        if (preg_grep("/{$requestMethod}/i", $methods)) {
79 9
            if ($routeString == '*') {
80 1
                return true;
81 9
            } elseif (isset($routeString[0]) && $routeString[0] == '@') {
82 2
                return preg_match('`' . substr($routeString, 1) . '`u', $requestUrl, $this->params);
83 7
            } elseif (($position = strpos($routeString, '[')) === false) {
84 1
                return strcmp($requestUrl, $routeString) === 0;
85
            }
86 6
            if (strncmp($requestUrl, $routeString, $position) !== 0) {
87 2
                return false;
88
            }
89
90 6
            return preg_match($this->compileRoute($routeString, $requestUrl), $requestUrl, $this->params);
91
        }
92
93 2
        return false;
94
    }
95
96
    /**
97
     * Compile the regex for a given route (EXPENSIVE)
98
     *
99
     * @param $routeString
100
     * @param $requestUrl
101
     *
102
     * @return string
103
     */
104 6
    private function compileRoute($routeString, $requestUrl)
105
    {
106 6
        $route = $this->getRoute($routeString, $requestUrl);
107
108 6
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
109 6
            $matchTypes = $this->matchTypes;
110 6
            foreach ($matches as $match) {
111 6
                $pattern = $this->getRoutePattern($matchTypes, $match[1], $match[2], $match[3], $match[4]);
112 6
                $route   = str_replace($match[0], $pattern, $route);
113 6
            }
114 6
        }
115
116 6
        return "`^$route$`u";
117
    }
118
119
    /**
120
     * @param $matchTypes
121
     * @param $pre
122
     * @param $type
123
     * @param $param
124
     * @param $optional
125
     *
126
     * @return string
127
     */
128 6
    private function getRoutePattern($matchTypes, $pre, $type, $param, $optional)
129
    {
130 6
        if (isset($matchTypes[$type])) {
131 6
            $type = $matchTypes[$type];
132 6
        }
133 6
        if ($pre === '.') {
134 1
            $pre = '\.';
135 1
        }
136
137
        //Older versions of PCRE require the 'P' in (?P<named>)
138
        return '(?:'
139 6
            . (!empty($pre) ? $pre : null)
140 6
            . '('
141 6
            . (!empty($param) ? "?P<$param>" : null)
142 6
            . $type
143 6
            . '))'
144 6
            . (!empty($optional) ? '?' : null);
145
    }
146
147
    /**
148
     * @param $routeString
149
     * @param $requestUrl
150
     *
151
     * @return bool|string
152
     */
153 6
    private function getRoute($routeString, $requestUrl)
154
    {
155 6
        $iPointer = $jPointer = 0;
156 6
        $nPointer = isset($routeString[0]) ? $routeString[0] : null;
157 6
        $regex = $route = false;
158
159
        // Find the longest non-regex substring and match it against the URI
160 6
        while (true) {
161 6
            if (!isset($routeString[$iPointer])) {
162 6
                break;
163
            }
164 6
            if ($regex === false) {
165 6
                if (!$this->isValidRouteRegex($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)) {
166
                    continue;
167
                }
168 6
                $jPointer++;
169 6
            }
170 6
            $route .= $routeString[$iPointer++];
171 6
        }
172
173 6
        return $route;
174
    }
175
176
    /**
177
     * @param $nPointer
178
     * @param $jPointer
179
     * @param $iPointer
180
     * @param $routeString
181
     * @param $requestUrl
182
     *
183
     * @return bool
184
     */
185 6
    private function isValidRouteRegex($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)
186
    {
187 6
        $cPointer = $nPointer;
188 6
        $regex = in_array($cPointer, array('[', '(', '.'));
189 6
        if (!$regex && isset($routeString[$iPointer+1])) {
190 6
            $nPointer = $routeString[$iPointer + 1];
191 6
            $regex = in_array($nPointer, array('?', '+', '*', '{'));
192 6
        }
193 6
        if (!$regex && $cPointer !== '/' && (!isset($requestUrl[$jPointer]) || $cPointer !== $requestUrl[$jPointer])) {
194
            return false;
195
        }
196 6
        return true;
197
    }
198
199
    /**
200
     * @return array
201
     */
202 9
    public function getParams()
203
    {
204 9
        return $this->params;
205
    }
206
207
    /**
208
     * @return array
209
     */
210
    public function getMatchTypes()
211
    {
212
        return $this->matchTypes;
213
    }
214
}
215