1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Mouf\Mvc\Splash\Store; |
4
|
|
|
|
5
|
|
|
use Mouf\Mvc\Splash\Services\SplashRouteInterface; |
6
|
|
|
use Mouf\Mvc\Splash\Utils\SplashException; |
7
|
|
|
/* |
8
|
|
|
* A SplashUrlNode is a datastructure optimised to navigate all possible URLs known to the application. |
9
|
|
|
* A SplashUrlNode represents all possible routes starting at the current position (just after a / in a URL) |
10
|
|
|
* |
11
|
|
|
* @author David Negrier |
12
|
|
|
*/ |
13
|
|
|
use Mouf\Mvc\Splash\Services\SplashRoute; |
14
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
15
|
|
|
|
16
|
|
|
class SplashUrlNode |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* An array of subnodes |
20
|
|
|
* The key is the string to be appended to the URL. |
21
|
|
|
* |
22
|
|
|
* @var array<string, SplashUrlNode> |
23
|
|
|
*/ |
24
|
|
|
private $children = array(); |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* An array of parameterized subnodes. |
28
|
|
|
* |
29
|
|
|
* @var array<string, SplashUrlNode> |
30
|
|
|
*/ |
31
|
|
|
private $parameterizedChildren = array(); |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* A list of callbacks (assicated to there HTTP method). |
35
|
|
|
* |
36
|
|
|
* @var array<string, SplashRoute> |
37
|
|
|
*/ |
38
|
|
|
private $callbacks = array(); |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* A list of callbacks (assicated to there HTTP method) finishing with "*". |
42
|
|
|
* |
43
|
|
|
* @var array<string, SplashRoute> |
44
|
|
|
*/ |
45
|
|
|
private $wildcardCallbacks = array(); |
46
|
|
|
|
47
|
|
|
public function registerCallback(SplashRouteInterface $callback) |
48
|
|
|
{ |
49
|
|
|
$this->addUrl(explode('/', $callback->getUrl()), $callback); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Registers a new URL. |
54
|
|
|
* The URL is passed as an array of strings (exploded on /). |
55
|
|
|
* |
56
|
|
|
* @param array<string> $urlParts |
57
|
|
|
*/ |
58
|
|
|
protected function addUrl(array $urlParts, SplashRouteInterface $callback) |
59
|
|
|
{ |
60
|
|
|
if (!empty($urlParts)) { |
61
|
|
|
$key = array_shift($urlParts); |
62
|
|
|
|
63
|
|
|
if ($key == '*') { |
64
|
|
|
// Wildcard URL |
65
|
|
|
if (!empty($urlParts)) { |
66
|
|
|
throw new SplashException('Sorry, the URL pattern /foo/*/bar is not supported. The wildcard (*) must be at the end of an URL'); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
$httpMethods = $callback->getHttpMethods(); |
70
|
|
View Code Duplication |
if (empty($httpMethods)) { |
|
|
|
|
71
|
|
|
if (isset($this->wildcardCallbacks[''])) { |
72
|
|
|
throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' is associated " |
73
|
|
|
."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->wildcardCallbacks['']->getControllerInstanceName().'->'.$this->wildcardCallbacks['']->getMethodName()); |
74
|
|
|
} |
75
|
|
|
$this->wildcardCallbacks[''] = $callback; |
76
|
|
|
} else { |
77
|
|
|
foreach ($httpMethods as $httpMethod) { |
78
|
|
|
if (isset($this->wildcardCallbacks[$httpMethod])) { |
79
|
|
|
throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' for HTTP method '".$httpMethod."' is associated " |
80
|
|
|
."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->wildcardCallbacks[$httpMethod]->getControllerInstanceName().'->'.$this->wildcardCallbacks[$httpMethod]->getMethodName()); |
81
|
|
|
} |
82
|
|
|
$this->wildcardCallbacks[$httpMethod] = $callback; |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
} elseif (strpos($key, '{') === 0 && strpos($key, '}') === strlen($key) - 1) { |
86
|
|
|
// Parameterized URL element |
87
|
|
|
$varName = substr($key, 1, strlen($key) - 2); |
88
|
|
|
|
89
|
|
|
if (!isset($this->parameterizedChildren[$varName])) { |
90
|
|
|
$this->parameterizedChildren[$varName] = new self(); |
91
|
|
|
} |
92
|
|
|
$this->parameterizedChildren[$varName]->addUrl($urlParts, $callback); |
93
|
|
|
} else { |
94
|
|
|
// Usual URL element |
95
|
|
|
if (!isset($this->children[$key])) { |
96
|
|
|
$this->children[$key] = new self(); |
97
|
|
|
} |
98
|
|
|
$this->children[$key]->addUrl($urlParts, $callback); |
99
|
|
|
} |
100
|
|
View Code Duplication |
} else { |
|
|
|
|
101
|
|
|
$httpMethods = $callback->getHttpMethods(); |
102
|
|
|
if (empty($httpMethods)) { |
103
|
|
|
if (isset($this->callbacks[''])) { |
104
|
|
|
throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' is associated " |
105
|
|
|
."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->callbacks['']->getControllerInstanceName().'->'.$this->callbacks['']->getMethodName()); |
106
|
|
|
} |
107
|
|
|
$this->callbacks[''] = $callback; |
108
|
|
|
} else { |
109
|
|
|
foreach ($httpMethods as $httpMethod) { |
110
|
|
|
if (isset($this->callbacks[$httpMethod])) { |
111
|
|
|
throw new SplashException("An error occured while looking at the list URL managed in Splash. The URL '".$callback->getUrl()."' for HTTP method '".$httpMethod."' is associated " |
112
|
|
|
."to 2 methods: \$".$callback->getControllerInstanceName().'->'.$callback->getMethodName()." and \$".$this->callbacks[$httpMethod]->getControllerInstanceName().'->'.$this->callbacks[$httpMethod]->getMethodName()); |
113
|
|
|
} |
114
|
|
|
$this->callbacks[$httpMethod] = $callback; |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Walks through the nodes to find the callback associated to the URL. |
122
|
|
|
* |
123
|
|
|
* @param string $url |
124
|
|
|
* @param ServerRequestInterface $request |
125
|
|
|
* |
126
|
|
|
* @return SplashRoute |
127
|
|
|
*/ |
128
|
|
|
public function walk($url, ServerRequestInterface $request) |
129
|
|
|
{ |
130
|
|
|
return $this->walkArray(explode('/', $url), $request, array()); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Walks through the nodes to find the callback associated to the URL. |
135
|
|
|
* |
136
|
|
|
* @param array $urlParts |
137
|
|
|
* @param ServerRequestInterface $request |
138
|
|
|
* @param array $parameters |
139
|
|
|
* @param SplashRoute $closestWildcardRoute The last wildcard (*) route encountered while navigating the tree. |
140
|
|
|
* |
141
|
|
|
* @return SplashRoute |
142
|
|
|
* |
143
|
|
|
* @throws SplashException |
144
|
|
|
*/ |
145
|
|
|
private function walkArray(array $urlParts, ServerRequestInterface $request, array $parameters, $closestWildcardRoute = null) |
146
|
|
|
{ |
147
|
|
|
$httpMethod = $request->getMethod(); |
148
|
|
|
|
149
|
|
|
if (isset($this->wildcardCallbacks[$httpMethod])) { |
150
|
|
|
$closestWildcardRoute = $this->wildcardCallbacks[$httpMethod]; |
151
|
|
|
$closestWildcardRoute->setFilledParameters($parameters); |
152
|
|
|
} elseif (isset($this->wildcardCallbacks[''])) { |
153
|
|
|
$closestWildcardRoute = $this->wildcardCallbacks['']; |
154
|
|
|
$closestWildcardRoute->setFilledParameters($parameters); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
if (!empty($urlParts)) { |
158
|
|
|
$key = array_shift($urlParts); |
159
|
|
|
if (isset($this->children[$key])) { |
160
|
|
|
return $this->children[$key]->walkArray($urlParts, $request, $parameters, $closestWildcardRoute); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
foreach ($this->parameterizedChildren as $varName => $splashUrlNode) { |
164
|
|
|
if (isset($parameters[$varName])) { |
165
|
|
|
throw new SplashException("An error occured while looking at the list URL managed in Splash. In a @URL annotation, the parameter '{$parameters[$varName]}' appears twice. That should never happen"); |
166
|
|
|
} |
167
|
|
|
$newParams = $parameters; |
168
|
|
|
$newParams[$varName] = $key; |
169
|
|
|
$result = $this->parameterizedChildren[$varName]->walkArray($urlParts, $request, $newParams, $closestWildcardRoute); |
170
|
|
|
if ($result !== null) { |
171
|
|
|
return $result; |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
// If we arrive here, there was no parametrized URL matching our objective |
176
|
|
|
return $closestWildcardRoute; |
177
|
|
|
} else { |
178
|
|
|
if (isset($this->callbacks[$httpMethod])) { |
179
|
|
|
$route = $this->callbacks[$httpMethod]; |
180
|
|
|
$route->setFilledParameters($parameters); |
181
|
|
|
|
182
|
|
|
return $route; |
183
|
|
|
} elseif (isset($this->callbacks[''])) { |
184
|
|
|
$route = $this->callbacks['']; |
185
|
|
|
$route->setFilledParameters($parameters); |
186
|
|
|
|
187
|
|
|
return $route; |
188
|
|
|
} else { |
189
|
|
|
return $closestWildcardRoute; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.