Passed
Branch release/v2.0.0 (67ff9b)
by Anatoly
02:09
created

path_parse()   F

Complexity

Conditions 40
Paths 20

Size

Total Lines 221
Code Lines 131

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 40
eloc 131
nc 20
nop 1
dl 0
loc 221
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
namespace Sunrise\Http\Router;
13
14
/**
15
 * Import classes
16
 */
17
use InvalidArgumentException;
18
19
/**
20
 * Import functions
21
 */
22
use function sprintf;
23
24
/**
25
 * Parses the given path
26
 *
27
 * @param string $path
28
 *
29
 * @return array
30
 *
31
 * @throws InvalidArgumentException
32
 */
33
function path_parse(string $path) : array
34
{
35
    static $cache = [];
36
37
    static $allowedAttributeNameChars = [
38
        '0' => 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1, '6' => 1, '7' => 1, '8' => 1, '9' => 1,
39
        'A' => 1, 'B' => 1, 'C' => 1, 'D' => 1, 'E' => 1, 'F' => 1, 'G' => 1, 'H' => 1, 'I' => 1, 'J' => 1,
40
        'K' => 1, 'L' => 1, 'M' => 1, 'N' => 1, 'O' => 1, 'P' => 1, 'Q' => 1, 'R' => 1, 'S' => 1, 'T' => 1,
41
        'U' => 1, 'V' => 1, 'W' => 1, 'X' => 1, 'Y' => 1, 'Z' => 1,
42
        'a' => 1, 'b' => 1, 'c' => 1, 'd' => 1, 'e' => 1, 'f' => 1, 'g' => 1, 'h' => 1, 'i' => 1, 'j' => 1,
43
        'k' => 1, 'l' => 1, 'm' => 1, 'n' => 1, 'o' => 1, 'p' => 1, 'q' => 1, 'r' => 1, 's' => 1, 't' => 1,
44
        'u' => 1, 'v' => 1, 'w' => 1, 'x' => 1, 'y' => 1, 'z' => 1,
45
        '_' => 1,
46
    ];
47
48
    static $charOptionalPieceStart = '(';
49
    static $charOptionalPieceEnd = ')';
50
    static $charAttributeStart = '{';
51
    static $charAttributeEnd = '}';
52
    static $charPatternStart = '<';
53
    static $charPatternEnd = '>';
54
55
    if (isset($cache[$path])) {
56
        return $cache[$path];
57
    }
58
59
    $attributes = [];
60
61
    $cursorPosition = -1;
62
    $cursorInOptionalPiece = false;
63
    $cursorInAttribute = false;
64
    $cursorInAttributeName = false;
65
    $cursorInPattern = false;
66
67
    $attributeIndex = -1;
68
    $attributePrototype = [
69
        'raw' => null,
70
        'withParentheses' => null,
71
        'name' => null,
72
        'pattern' => null,
73
        'isOptional' => false,
74
        'startPosition' => -1,
75
        'endPosition' => -1,
76
    ];
77
78
    $parenthesesBusy = false;
79
    $parenthesesLeft = null;
80
    $parenthesesRight = null;
81
82
    while (true) {
83
        $cursorPosition++;
84
85
        if (!isset($path[$cursorPosition])) {
86
            break;
87
        }
88
89
        $char = $path[$cursorPosition];
90
91
        if ($charOptionalPieceStart === $char && !$cursorInAttribute) {
92
            if ($cursorInOptionalPiece) {
93
                throw new InvalidArgumentException(
94
                    sprintf('[%s:%d] parentheses inside parentheses are not allowed.', $path, $cursorPosition)
95
                );
96
            }
97
98
            $cursorInOptionalPiece = true;
99
100
            continue;
101
        }
102
103
        if ($charAttributeStart === $char && !$cursorInPattern) {
104
            if ($cursorInAttribute) {
105
                throw new InvalidArgumentException(
106
                    sprintf('[%s:%d] braces inside braces are not allowed.', $path, $cursorPosition)
107
                );
108
            }
109
110
            if ($parenthesesBusy) {
111
                throw new InvalidArgumentException(
112
                    sprintf('[%s:%d] multiple attributes inside parentheses are not allowed.', $path, $cursorPosition)
113
                );
114
            }
115
116
            if ($cursorInOptionalPiece) {
117
                $parenthesesBusy = true;
118
            }
119
120
            $cursorInAttribute = true;
121
            $cursorInAttributeName = true;
122
            $attributeIndex++;
123
            $attributes[$attributeIndex] = $attributePrototype;
124
            $attributes[$attributeIndex]['raw'] .= $char;
125
            $attributes[$attributeIndex]['isOptional'] = $cursorInOptionalPiece;
126
            $attributes[$attributeIndex]['startPosition'] = $cursorPosition;
127
128
            continue;
129
        }
130
131
        if ($charPatternStart === $char && $cursorInAttribute) {
132
            if ($cursorInPattern) {
133
                throw new InvalidArgumentException(
134
                    sprintf('[%s:%d] less than char inside a pattern is not allowed.', $path, $cursorPosition)
135
                );
136
            }
137
138
            $cursorInPattern = true;
139
            $cursorInAttributeName = false;
140
            $attributes[$attributeIndex]['raw'] .= $char;
141
142
            continue;
143
        }
144
145
        if ($charPatternEnd === $char && $cursorInAttribute) {
146
            if (!$cursorInPattern) {
147
                throw new InvalidArgumentException(
148
                    sprintf('[%s:%d] greater than char inside a pattern is not allowed.', $path, $cursorPosition)
149
                );
150
            }
151
152
            if (null === $attributes[$attributeIndex]['pattern']) {
153
                throw new InvalidArgumentException(
154
                    sprintf('[%s:%d] an attribute pattern is empty.', $path, $cursorPosition)
155
                );
156
            }
157
158
            $cursorInPattern = false;
159
            $attributes[$attributeIndex]['raw'] .= $char;
160
161
            continue;
162
        }
163
164
        if ($charAttributeEnd === $char && !$cursorInPattern) {
165
            if (!$cursorInAttribute) {
166
                throw new InvalidArgumentException(
167
                    sprintf('[%s:%d] at position %2$d an extra closing brace was found.', $path, $cursorPosition)
168
                );
169
            }
170
171
            if (null === $attributes[$attributeIndex]['name']) {
172
                throw new InvalidArgumentException(
173
                    sprintf('[%s:%d] an attribute name is empty.', $path, $cursorPosition)
174
                );
175
            }
176
177
            $cursorInAttribute = false;
178
            $cursorInAttributeName = false;
179
            $attributes[$attributeIndex]['raw'] .= $char;
180
            $attributes[$attributeIndex]['endPosition'] = $cursorPosition;
181
182
            continue;
183
        }
184
185
        if ($charOptionalPieceEnd === $char && !$cursorInAttribute) {
186
            if (!$cursorInOptionalPiece) {
187
                throw new InvalidArgumentException(
188
                    sprintf('[%s:%d] at position %2$d an extra closing parenthesis was found.', $path, $cursorPosition)
189
                );
190
            }
191
192
            if ($parenthesesBusy) {
193
                $attributes[$attributeIndex]['withParentheses'] = '(' . $parenthesesLeft;
194
                $attributes[$attributeIndex]['withParentheses'] .= $attributes[$attributeIndex]['raw'];
195
                $attributes[$attributeIndex]['withParentheses'] .= $parenthesesRight . ')';
196
            }
197
198
            $cursorInOptionalPiece = false;
199
            $parenthesesBusy = false;
200
            $parenthesesLeft = null;
201
            $parenthesesRight = null;
202
203
            continue;
204
        }
205
206
        if ($cursorInAttribute) {
207
            $attributes[$attributeIndex]['raw'] .= $char;
208
        }
209
210
        if ($cursorInAttributeName) {
211
            if (!isset($allowedAttributeNameChars[$char])) {
212
                throw new InvalidArgumentException(
213
                    sprintf('[%s:%d] an attribute name contains invalid character.', $path, $cursorPosition)
214
                );
215
            }
216
217
            $attributes[$attributeIndex]['name'] .= $char;
218
        }
219
220
        if ($cursorInPattern) {
221
            $attributes[$attributeIndex]['pattern'] .= $char;
222
        }
223
224
        if ($cursorInOptionalPiece && !$cursorInAttribute && !$parenthesesBusy) {
225
            $parenthesesLeft .= $char;
226
        }
227
228
        if ($cursorInOptionalPiece && !$cursorInAttribute && $parenthesesBusy) {
229
            $parenthesesRight .= $char;
230
        }
231
    }
232
233
    if ($cursorInOptionalPiece) {
234
        throw new InvalidArgumentException(
235
            sprintf('[%s] the route path contains non-closed parentheses.', $path)
236
        );
237
    }
238
239
    if ($cursorInAttribute) {
240
        throw new InvalidArgumentException(
241
            sprintf('[%s] the route path contains non-closed attribute.', $path)
242
        );
243
    }
244
245
    if ($cursorInPattern) {
246
        throw new InvalidArgumentException(
247
            sprintf('[%s] the route path contains non-closed pattern.', $path)
248
        );
249
    }
250
251
    $cache[$path] = $attributes;
252
253
    return $attributes;
254
}
255