Passed
Push — master ( 723c8d...5629e4 )
by Ch
02:31
created

RouterParser::getMatchTypes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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 $method
72
     * @param $requestMethod
73
     * @param string|array $routeString
74
     * @param $requestUrl
75
     *
76
     * @return mixed
77
     */
78
    public function methodMatch($method, $requestMethod, $routeString, $requestUrl)
79
    {
80
        $method = strtolower($method);
81
        $requestMethod = strtolower($requestMethod);
82
        $methods = explode('|', $method);
83
84
        if(in_array($requestMethod, $methods)) {
85
            if($routeString == '*') {
86
                return true;
87
            }
88
            elseif(isset($routeString[0]) && $routeString[0] == '@') {
89
                $match = preg_match('`' . substr($routeString, 1) . '`u', $requestUrl, $this->params);
0 ignored issues
show
Bug introduced by
It seems like $routeString can also be of type array; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

89
                $match = preg_match('`' . substr(/** @scrutinizer ignore-type */ $routeString, 1) . '`u', $requestUrl, $this->params);
Loading history...
90
                return $match;
91
            }
92
            elseif (($position = strpos($routeString, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like $routeString can also be of type array; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

92
            elseif (($position = strpos(/** @scrutinizer ignore-type */ $routeString, '[')) === false) {
Loading history...
93
                return strcmp($requestUrl, $routeString) === 0;
94
            }
95
            else {
96
                if (strncmp($requestUrl, $routeString, $position) !== 0) {
0 ignored issues
show
Bug introduced by
It seems like $routeString can also be of type array; however, parameter $str2 of strncmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

96
                if (strncmp($requestUrl, /** @scrutinizer ignore-type */ $routeString, $position) !== 0) {
Loading history...
97
                    return false;
98
                }
99
                $regex = $this->compileRoute($routeString, $requestUrl);
100
                return preg_match($regex, $requestUrl, $this->params);
101
            }
102
        }
103
104
        return false;
105
    }
106
107
    /**
108
     * Compile the regex for a given route (EXPENSIVE)
109
     */
110
    private function compileRoute($routeString, $requestUrl)
111
    {
112
        $route = $this->getRoute($routeString, $requestUrl);
113
114
        if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
115
            $matchTypes = $this->matchTypes;
116
            foreach ($matches as $match) {
117
                list($block, $pre, $type, $param, $optional) = $match;
118
                $pattern = $this->getRoutePattern($matchTypes, $pre, $type, $param, $optional);
119
                $route = str_replace($block, $pattern, $route);
120
            }
121
        }
122
123
        return "`^$route$`u";
124
    }
125
126
    private function getRoutePattern($matchTypes, $pre, $type, $param, $optional)
127
    {
128
        if (isset($matchTypes[$type])) {
129
            $type = $matchTypes[$type];
130
        }
131
        if ($pre === '.') {
132
            $pre = '\.';
133
        }
134
135
        //Older versions of PCRE require the 'P' in (?P<named>)
136
        return '(?:'
137
            . ($pre !== '' ? $pre : null)
138
            . '('
139
            . ($param !== '' ? "?P<$param>" : null)
140
            . $type
141
            . '))'
142
            . ($optional !== '' ? '?' : null);
143
    }
144
145
    /**
146
     * @param $routeString
147
     * @param $requestUrl
148
     *
149
     * @return bool|string
150
     */
151
    private function getRoute($routeString, $requestUrl)
152
    {
153
        $iPointer = $jPointer = 0;
154
        $nPointer = isset($routeString[0]) ? $routeString[0] : null;
155
        $regex = $route = false;
156
157
        // Find the longest non-regex substring and match it against the URI
158
        while (true) {
159
            if (!isset($routeString[$iPointer])) {
160
                break;
161
            }
162
            if ($regex === false) {
163
                if(!$this->getRouteRegexCheck($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)) {
164
                    continue;
165
                }
166
                $jPointer++;
167
            }
168
            $route .= $routeString[$iPointer++];
169
        }
170
171
        return $route;
172
    }
173
174
    private function getRouteRegexCheck($nPointer, $jPointer, $iPointer, $routeString, $requestUrl)
175
    {
176
        $cPointer = $nPointer;
177
        $regex = in_array($cPointer, array('[', '(', '.'));
178
        if (!$regex && isset($routeString[$iPointer+1])) {
179
            $nPointer = $routeString[$iPointer + 1];
180
            $regex = in_array($nPointer, array('?', '+', '*', '{'));
181
        }
182
        if (!$regex && $cPointer !== '/' && (!isset($requestUrl[$jPointer]) || $cPointer !== $requestUrl[$jPointer])) {
183
            return false;
184
        }
185
        return true;
186
    }
187
188
    /**
189
     * @return array
190
     */
191
    public function getParams()
192
    {
193
        return $this->params;
194
    }
195
196
    public function getMatchTypes()
197
    {
198
        return $this->matchTypes;
199
    }
200
}
201