|
12 | 12 | */
|
13 | 13 | class Std implements RouteParser {
|
14 | 14 | const VARIABLE_REGEX = <<<'REGEX'
|
15 |
| -~\{ |
| 15 | +\{ |
16 | 16 | \s* ([a-zA-Z][a-zA-Z0-9_]*) \s*
|
17 | 17 | (?:
|
18 | 18 | : \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
|
19 | 19 | )?
|
20 |
| -\}~x |
| 20 | +\} |
21 | 21 | REGEX;
|
22 | 22 | const DEFAULT_DISPATCH_REGEX = '[^/]+';
|
23 | 23 |
|
24 | 24 | public function parse($route) {
|
25 | 25 | $routeWithoutClosingOptionals = rtrim($route, ']');
|
26 | 26 | $numOptionals = strlen($route) - strlen($routeWithoutClosingOptionals);
|
27 |
| - $routeParts = $this->parsePlaceholders($routeWithoutClosingOptionals); |
28 |
| - if ($numOptionals === 0) { |
29 |
| - return [$routeParts]; |
| 27 | + |
| 28 | + // Split on [ while skipping placeholders |
| 29 | + $segments = preg_split('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \[~x', $routeWithoutClosingOptionals); |
| 30 | + if ($numOptionals !== count($segments) - 1) { |
| 31 | + throw new BadRouteException("Number of opening '[' and closing ']' does not match"); |
30 | 32 | }
|
31 |
| - return $this->handleOptionals($routeParts, $numOptionals); |
32 |
| - } |
33 | 33 |
|
34 |
| - private function handleOptionals($routeParts, $numOptionals) { |
| 34 | + $currentRoute = ''; |
35 | 35 | $routeDatas = [];
|
36 |
| - $currentRouteData = []; |
37 |
| - foreach ($routeParts as $part) { |
38 |
| - // skip placeholders |
39 |
| - if (!is_string($part)) { |
40 |
| - $currentRouteData[] = $part; |
41 |
| - continue; |
| 36 | + foreach ($segments as $segment) { |
| 37 | + if ($segment === '') { |
| 38 | + throw new BadRouteException("Empty optional part"); |
42 | 39 | }
|
43 | 40 |
|
44 |
| - $segments = explode('[', $part); |
45 |
| - $currentNumOptionals = count($segments) - 1; |
46 |
| - $numOptionals -= $currentNumOptionals; |
47 |
| - if ($numOptionals < 0) { |
48 |
| - throw new BadRouteException("Found more opening '[' than closing ']'"); |
49 |
| - } |
50 |
| - |
51 |
| - $currentPart = ''; |
52 |
| - foreach ($segments as $i => $addPart) { |
53 |
| - if ($addPart === '') { |
54 |
| - if ($currentPart !== '') { |
55 |
| - throw new BadRouteException("Empty optional part"); |
56 |
| - } |
57 |
| - $routeDatas[] = $currentRouteData; |
58 |
| - continue; |
59 |
| - } |
60 |
| - |
61 |
| - $currentPart .= $addPart; |
62 |
| - if ($i !== $currentNumOptionals) { |
63 |
| - $routeData = $currentRouteData; |
64 |
| - $routeData[] = $currentPart; |
65 |
| - $routeDatas[] = $routeData; |
66 |
| - } else { |
67 |
| - $currentRouteData[] = $currentPart; |
68 |
| - } |
69 |
| - } |
70 |
| - } |
71 |
| - |
72 |
| - $routeDatas[] = $currentRouteData; |
73 |
| - if ($numOptionals > 0) { |
74 |
| - throw new BadRouteException("Found more closing ']' than opening '['"); |
| 41 | + $currentRoute .= $segment; |
| 42 | + $routeDatas[] = $this->parsePlaceholders($currentRoute); |
75 | 43 | }
|
76 |
| - |
77 | 44 | return $routeDatas;
|
78 | 45 | }
|
79 | 46 |
|
80 | 47 | /**
|
81 |
| - * Parses a route string only considering {placeholders}, but ignoring [optionals]. |
| 48 | + * Parses a route string that does not contain optional segments. |
82 | 49 | */
|
83 | 50 | private function parsePlaceholders($route) {
|
84 | 51 | if (!preg_match_all(
|
85 |
| - self::VARIABLE_REGEX, $route, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER |
| 52 | + '~' . self::VARIABLE_REGEX . '~x', $route, $matches, |
| 53 | + PREG_OFFSET_CAPTURE | PREG_SET_ORDER |
86 | 54 | )) {
|
87 | 55 | return [$route];
|
88 | 56 | }
|
|
0 commit comments