Passed
Push — master ( 101335...f38744 )
by Ch
06:52 queued 04:31
created

RouterParser::isValidRouteRegex()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 8
nc 4
nop 5
dl 0
loc 12
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
                $pre   = $match[1];
52
                $param = $match[3];
53
                $block = $pre ? substr($match[0], 1) : $match[0];
54
55
                if (isset($params[$param])) {
56
                    $url = str_replace($block, $params[$param], $url);
57
                } elseif ($match[4]) {
58
                    $url = str_replace($pre . $block, '', $url);
59
                }
60
            }
61
        }
62
63
        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
    public function methodMatch($method, $requestMethod, $routeString, $requestUrl)
75
    {
76
        $methods = explode('|', $method);
77
78
        if (preg_grep("/{$requestMethod}/i", $methods)) {
79
            if ($routeString == '*') {
80
                return true;
81
            } elseif (isset($routeString[0]) && $routeString[0] == '@') {
82
                return preg_match('`' . substr($routeString, 1) . '`u', $requestUrl, $this->params);
83
            } elseif (($position = strpos($routeString, '[')) === false) {
84
                return strcmp($requestUrl, $routeString) === 0;
85
            }
86
            if (strncmp($requestUrl, $routeString, $position) !== 0) {
87
                return false;
88
            }
89
90
            return preg_match($this->compileRoute($routeString, $requestUrl), $requestUrl, $this->params);
91
        }
92
93
        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
    private function compileRoute($routeString, $requestUrl)
105
    {
106
        $route = $this->getRoute($routeString, $requestUrl);
107
108
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
109
            $matchTypes = $this->matchTypes;
110
            foreach ($matches as $match) {
111
                $pattern = $this->getRoutePattern($matchTypes, $match[1], $match[2], $match[3], $match[4]);
112
                $route   = str_replace($match[0], $pattern, $route);
113
            }
114
        }
115
116
        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
    private function getRoutePattern($matchTypes, $pre, $type, $param, $optional)
129
    {
130
        if (isset($matchTypes[$type])) {
131
            $type = $matchTypes[$type];
132
        }
133
        if ($pre === '.') {
134
            $pre = '\.';
135
        }
136
137
        //Older versions of PCRE require the 'P' in (?P<named>)
138
        return '(?:'
139
            . (!empty($pre) ? $pre : null)
140
            . '('
141
            . (!empty($param) ? "?P<$param>" : null)
142
            . $type
143
            . '))'
144
            . (!empty($optional) ? '?' : null);
145
    }
146
147
    /**
148
     * @param $routeString
149
     * @param $requestUrl
150
     *
151
     * @return bool|string
152
     */
153
    private function getRoute($routeString, $requestUrl)
154
    {
155
        $iPointer = $jPointer = 0;
156
        $nPointer = isset($routeString[0]) ? $routeString[0] : null;
157
        $regex = $route = false;
158
159
        // Find the longest non-regex substring and match it against the URI
160
        while (true) {
161
            if (!isset($routeString[$iPointer])) {
162
                break;
163
            }
164
            if ($regex === false) {
165
                if (!$this->isValidRouteRegex($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)) {
166
                    continue;
167
                }
168
                $jPointer++;
169
            }
170
            $route .= $routeString[$iPointer++];
171
        }
172
173
        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
    private function isValidRouteRegex($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)
186
    {
187
        $cPointer = $nPointer;
188
        $regex = in_array($cPointer, array('[', '(', '.'));
189
        if (!$regex && isset($routeString[$iPointer+1])) {
190
            $nPointer = $routeString[$iPointer + 1];
191
            $regex = in_array($nPointer, array('?', '+', '*', '{'));
192
        }
193
        if (!$regex && $cPointer !== '/' && (!isset($requestUrl[$jPointer]) || $cPointer !== $requestUrl[$jPointer])) {
194
            return false;
195
        }
196
        return true;
197
    }
198
199
    /**
200
     * @return array
201
     */
202
    public function getParams()
203
    {
204
        return $this->params;
205
    }
206
207
    /**
208
     * @return array
209
     */
210
    public function getMatchTypes()
211
    {
212
        return $this->matchTypes;
213
    }
214
}
215