Passed
Push — master ( 233819...77d4a0 )
by Ch
02:28
created

RouterParser::methodMatch()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 6
nop 4
dl 0
loc 20
rs 8.2222
c 0
b 0
f 0
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
    public function __construct($matchTypes = [])
22
    {
23
        $this->setMatchTypes($matchTypes);
24
    }
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
    public function setMatchTypes($matchTypes)
32
    {
33
        $this->matchTypes = array_merge($this->matchTypes, $matchTypes);
34
    }
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
    public function generate($basePath, $route, array $params)
46
    {
47
        $url = $basePath . $route;
48
49
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
50
            foreach ($matches as $match) {
51
                $block  = $match[0];
52
                $pre    = $match[1];
53
                $param  = $match[3];
54
55
                if ($pre) {
56
                    $block = substr($block, 1);
57
                }
58
59
                if (isset($params[$param])) {
60
                    $url = str_replace($block, $params[$param], $url);
61
                } elseif ($match[4]) {
62
                    $url = str_replace($pre . $block, '', $url);
63
                }
64
            }
65
        }
66
67
        return $url;
68
    }
69
70
    /**
71
     * @param string $method
72
     * @param string $requestMethod
73
     * @param string $routeString
74
     * @param string $requestUrl
75
     *
76
     * @return mixed
77
     */
78
    public function methodMatch($method, $requestMethod, $routeString, $requestUrl)
79
    {
80
        $methods = explode('|', $method);
81
82
        if (preg_grep("/{$requestMethod}/i", $methods)) {
83
            if ($routeString == '*') {
84
                return true;
85
            } elseif (isset($routeString[0]) && $routeString[0] == '@') {
86
                return preg_match('`' . substr($routeString, 1) . '`u', $requestUrl, $this->params);
87
            } elseif (($position = strpos($routeString, '[')) === false) {
88
                return strcmp($requestUrl, $routeString) === 0;
89
            }
90
            if (strncmp($requestUrl, $routeString, $position) !== 0) {
91
                return false;
92
            }
93
94
            return preg_match($this->compileRoute($routeString, $requestUrl), $requestUrl, $this->params);
95
        }
96
97
        return false;
98
    }
99
100
    /**
101
     * Compile the regex for a given route (EXPENSIVE)
102
     *
103
     * @param $routeString
104
     * @param $requestUrl
105
     *
106
     * @return string
107
     */
108
    private function compileRoute($routeString, $requestUrl)
109
    {
110
        $route = $this->getRoute($routeString, $requestUrl);
111
112
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
113
            $matchTypes = $this->matchTypes;
114
            foreach ($matches as $match) {
115
                list($block, $pre, $type, $param, $optional) = $match;
116
                $pattern = $this->getRoutePattern($matchTypes, $pre, $type, $param, $optional);
117
                $route = str_replace($block, $pattern, $route);
118
            }
119
        }
120
121
        return "`^$route$`u";
122
    }
123
124
    /**
125
     * @param $matchTypes
126
     * @param $pre
127
     * @param $type
128
     * @param $param
129
     * @param $optional
130
     *
131
     * @return string
132
     */
133
    private function getRoutePattern($matchTypes, $pre, $type, $param, $optional)
134
    {
135
        if (isset($matchTypes[$type])) {
136
            $type = $matchTypes[$type];
137
        }
138
        if ($pre === '.') {
139
            $pre = '\.';
140
        }
141
142
        //Older versions of PCRE require the 'P' in (?P<named>)
143
        return '(?:'
144
            . ($pre !== '' ? $pre : null)
145
            . '('
146
            . ($param !== '' ? "?P<$param>" : null)
147
            . $type
148
            . '))'
149
            . ($optional !== '' ? '?' : null);
150
    }
151
152
    /**
153
     * @param $routeString
154
     * @param $requestUrl
155
     *
156
     * @return bool|string
157
     */
158
    private function getRoute($routeString, $requestUrl)
159
    {
160
        $iPointer = $jPointer = 0;
161
        $nPointer = isset($routeString[0]) ? $routeString[0] : null;
162
        $regex = $route = false;
163
164
        // Find the longest non-regex substring and match it against the URI
165
        while (true) {
166
            if (!isset($routeString[$iPointer])) {
167
                break;
168
            }
169
            if ($regex === false) {
170
                if (!$this->getRouteRegexCheck($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)) {
171
                    continue;
172
                }
173
                $jPointer++;
174
            }
175
            $route .= $routeString[$iPointer++];
176
        }
177
178
        return $route;
179
    }
180
181
    /**
182
     * @param $nPointer
183
     * @param $jPointer
184
     * @param $iPointer
185
     * @param $routeString
186
     * @param $requestUrl
187
     *
188
     * @return bool
189
     */
190
    private function getRouteRegexCheck($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)
191
    {
192
        $cPointer = $nPointer;
193
        $regex = in_array($cPointer, array('[', '(', '.'));
194
        if (!$regex && isset($routeString[$iPointer+1])) {
195
            $nPointer = $routeString[$iPointer + 1];
196
            $regex = in_array($nPointer, array('?', '+', '*', '{'));
197
        }
198
        if (!$regex && $cPointer !== '/' && (!isset($requestUrl[$jPointer]) || $cPointer !== $requestUrl[$jPointer])) {
199
            return false;
200
        }
201
        return true;
202
    }
203
204
    /**
205
     * @return array
206
     */
207
    public function getParams()
208
    {
209
        return $this->params;
210
    }
211
212
    /**
213
     * @return array
214
     */
215
    public function getMatchTypes()
216
    {
217
        return $this->matchTypes;
218
    }
219
}
220