1 | <?php |
||||||
2 | /** |
||||||
3 | * DronePHP (http://www.dronephp.com) |
||||||
4 | * |
||||||
5 | * @link http://github.com/Pleets/DronePHP |
||||||
6 | * @copyright Copyright (c) 2016-2018 Pleets. (http://www.pleets.org) |
||||||
7 | * @license http://www.dronephp.com/license |
||||||
8 | * @author Darío Rivera <[email protected]> |
||||||
9 | */ |
||||||
10 | |||||||
11 | namespace Drone\Mvc; |
||||||
12 | |||||||
13 | /** |
||||||
14 | * Router class |
||||||
15 | * |
||||||
16 | * This class build the route and calls to specific application controller |
||||||
17 | */ |
||||||
18 | class Router |
||||||
19 | { |
||||||
20 | /** |
||||||
21 | * List of routes |
||||||
22 | * |
||||||
23 | * @var array |
||||||
24 | */ |
||||||
25 | private $routes = []; |
||||||
26 | |||||||
27 | /** |
||||||
28 | * The Identifiers builds the route |
||||||
29 | * |
||||||
30 | * @var array |
||||||
31 | */ |
||||||
32 | private $identifiers; |
||||||
33 | |||||||
34 | /** |
||||||
35 | * Default identifiers |
||||||
36 | * |
||||||
37 | * @var array |
||||||
38 | */ |
||||||
39 | private $defaults; |
||||||
40 | |||||||
41 | /** |
||||||
42 | * Controller instance |
||||||
43 | * |
||||||
44 | * @var AbstractController |
||||||
45 | */ |
||||||
46 | private $controller; |
||||||
47 | |||||||
48 | /** |
||||||
49 | * Indicates how the class name could be matched |
||||||
50 | * |
||||||
51 | * @var callable |
||||||
52 | */ |
||||||
53 | private $classNameBuilder; |
||||||
54 | |||||||
55 | /** |
||||||
56 | * Zend\Router implementation |
||||||
57 | * |
||||||
58 | * @var \Zend\Router\SimpleRouteStack |
||||||
59 | */ |
||||||
60 | private $zendRouter; |
||||||
61 | |||||||
62 | /** |
||||||
63 | * Returns all routes built |
||||||
64 | * |
||||||
65 | * @return array |
||||||
66 | */ |
||||||
67 | public function getRoutes() |
||||||
68 | { |
||||||
69 | return $this->routes; |
||||||
70 | } |
||||||
71 | |||||||
72 | /** |
||||||
73 | * Returns all identifiers |
||||||
74 | * |
||||||
75 | * @return array |
||||||
76 | */ |
||||||
77 | public function getIdentifiers() |
||||||
78 | { |
||||||
79 | return $this->identifiers; |
||||||
80 | } |
||||||
81 | |||||||
82 | /** |
||||||
83 | * Returns default identifiers |
||||||
84 | * |
||||||
85 | * @return array |
||||||
86 | */ |
||||||
87 | public function getDefaults() |
||||||
88 | { |
||||||
89 | return $this->defaults; |
||||||
90 | } |
||||||
91 | |||||||
92 | /** |
||||||
93 | * Returns the controller instance |
||||||
94 | * |
||||||
95 | * @throws \RuntimeException |
||||||
96 | * |
||||||
97 | * @return AbstractController |
||||||
98 | */ |
||||||
99 | 5 | public function getController() |
|||||
100 | { |
||||||
101 | 5 | if (is_null($this->controller)) { |
|||||
102 | throw new \RuntimeException("No controller matched, try to match first."); |
||||||
103 | } |
||||||
104 | |||||||
105 | 5 | return $this->controller; |
|||||
106 | } |
||||||
107 | |||||||
108 | /** |
||||||
109 | * Returns the class name builder function |
||||||
110 | * |
||||||
111 | * @return callable |
||||||
112 | */ |
||||||
113 | public function getClassNameBuilder() |
||||||
114 | { |
||||||
115 | return $this->classNameBuilder; |
||||||
116 | } |
||||||
117 | |||||||
118 | /** |
||||||
119 | * Returns the Zend\Router\SimpleRouteStack object |
||||||
120 | * |
||||||
121 | * @return \Zend\Router\SimpleRouteStack |
||||||
122 | */ |
||||||
123 | public function getZendRouter() |
||||||
124 | { |
||||||
125 | return $this->zendRouter; |
||||||
126 | } |
||||||
127 | |||||||
128 | /** |
||||||
129 | * Sets identifiers |
||||||
130 | * |
||||||
131 | * @param string $module |
||||||
132 | * @param string $controller |
||||||
133 | * @param string $view |
||||||
134 | * |
||||||
135 | * @return null |
||||||
136 | */ |
||||||
137 | 6 | public function setIdentifiers($module, $controller, $view) |
|||||
138 | { |
||||||
139 | 6 | $identifiers = ["module" => $module, "controller" => $controller, "view" => $view]; |
|||||
140 | |||||||
141 | 6 | foreach ($identifiers as $key => $identifier) { |
|||||
142 | 6 | if (!is_string($identifier)) { |
|||||
143 | 6 | throw new \InvalidArgumentException("Invalid type given for '$key'. String expected."); |
|||||
144 | } |
||||||
145 | } |
||||||
146 | |||||||
147 | 6 | $this->identifiers = [ |
|||||
148 | 6 | "module" => $module, |
|||||
149 | 6 | "controller" => $controller, |
|||||
150 | 6 | "view" => $view, |
|||||
151 | ]; |
||||||
152 | 6 | } |
|||||
153 | |||||||
154 | /** |
||||||
155 | * Sets default identifiers |
||||||
156 | * |
||||||
157 | * @param string $module |
||||||
158 | * @param string $controller |
||||||
159 | * @param string $view |
||||||
160 | * |
||||||
161 | * @return null |
||||||
162 | */ |
||||||
163 | 1 | public function setDefaults($module, $controller, $view) |
|||||
164 | { |
||||||
165 | 1 | $identifiers = ["module" => $module, "controller" => $controller, "view" => $view]; |
|||||
166 | |||||||
167 | 1 | foreach ($identifiers as $key => $identifier) { |
|||||
168 | 1 | if (!is_string($identifier)) { |
|||||
169 | 1 | throw new \InvalidArgumentException("Invalid type given for '$key'. String expected."); |
|||||
170 | } |
||||||
171 | } |
||||||
172 | |||||||
173 | 1 | $this->defaults = [ |
|||||
174 | 1 | "module" => $module, |
|||||
175 | 1 | "controller" => $controller, |
|||||
176 | 1 | "view" => $view, |
|||||
177 | ]; |
||||||
178 | 1 | } |
|||||
179 | |||||||
180 | /** |
||||||
181 | * Sets the class name builder function |
||||||
182 | * |
||||||
183 | * @param callable $builder |
||||||
184 | * |
||||||
185 | * @return null |
||||||
186 | */ |
||||||
187 | 6 | public function setClassNameBuilder(callable $builder) |
|||||
188 | { |
||||||
189 | 6 | $this->classNameBuilder = $builder; |
|||||
190 | 6 | } |
|||||
191 | |||||||
192 | /** |
||||||
193 | * Constructor |
||||||
194 | * |
||||||
195 | * @param array $routes |
||||||
196 | */ |
||||||
197 | 6 | public function __construct(array $routes = []) |
|||||
198 | { |
||||||
199 | 6 | if (count($routes)) { |
|||||
200 | foreach ($routes as $route) { |
||||||
201 | $this->addRoute($route); |
||||||
202 | } |
||||||
203 | } |
||||||
204 | |||||||
205 | # schema for identifiers |
||||||
206 | 6 | $this->identifiers = [ |
|||||
207 | "module" => '', |
||||||
208 | "controller" => '', |
||||||
209 | "view" => '', |
||||||
210 | ]; |
||||||
211 | |||||||
212 | 6 | $this->defaults = [ |
|||||
213 | "module" => '', |
||||||
214 | "controller" => '', |
||||||
215 | "view" => '', |
||||||
216 | ]; |
||||||
217 | |||||||
218 | # default class name builder |
||||||
219 | $this->setClassNameBuilder(function ($module, $class) { |
||||||
220 | 2 | return "\\$module\\$class"; |
|||||
221 | 6 | }); |
|||||
222 | |||||||
223 | 6 | $this->zendRouter = new \Zend\Router\SimpleRouteStack(); |
|||||
224 | 6 | } |
|||||
225 | |||||||
226 | /** |
||||||
227 | * Builds the current route and calls the controller |
||||||
228 | * |
||||||
229 | * @throws Exception\PageNotFoundException |
||||||
230 | * @throws Exception\RouteNotFoundException |
||||||
231 | * @throws \LogicException |
||||||
232 | * |
||||||
233 | * @return null |
||||||
234 | */ |
||||||
235 | 6 | public function match() |
|||||
236 | { |
||||||
237 | 6 | if (!is_callable($this->classNameBuilder)) { |
|||||
238 | throw \LogicException("No class name builder found"); |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
239 | } |
||||||
240 | |||||||
241 | /* |
||||||
242 | * Key value pairs builder: |
||||||
243 | * Searches for the pattern /var1/value1/var2/value2 and converts it to var1 => value1, var2 => value2 |
||||||
244 | */ |
||||||
245 | 6 | if (array_key_exists('params', $_GET)) { |
|||||
246 | $keypairs = $this->parseRequestParameters($_GET["params"]); |
||||||
0 ignored issues
–
show
The method
parseRequestParameters() does not exist on Drone\Mvc\Router .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
247 | unset($_GET["params"]); |
||||||
248 | $_GET = array_merge($_GET, $keypairs); |
||||||
249 | } |
||||||
250 | |||||||
251 | /* |
||||||
252 | * Route builder: |
||||||
253 | * The route is built by default from the URL as follow |
||||||
254 | * www.example.com/module/controller/view |
||||||
255 | */ |
||||||
256 | |||||||
257 | 6 | $match = false; |
|||||
258 | |||||||
259 | 6 | if (count($this->routes)) { |
|||||
260 | 6 | foreach ($this->routes as $key => $route) { |
|||||
261 | 6 | if ($route["module"] == $this->identifiers["module"] && |
|||||
262 | 6 | $route["controller"] == $this->identifiers["controller"] && |
|||||
263 | 6 | $route["view"] == $this->identifiers["view"] |
|||||
264 | ) { |
||||||
265 | 5 | $module = $route["module"]; |
|||||
266 | 5 | $controller = $route["controller"]; |
|||||
267 | 5 | $view = $route["view"]; |
|||||
268 | |||||||
269 | 5 | $match = true; |
|||||
270 | 6 | break; |
|||||
271 | } |
||||||
272 | } |
||||||
273 | } |
||||||
274 | |||||||
275 | 6 | if (count($this->defaults) && !$match) { |
|||||
276 | 2 | if (!empty($this->defaults["module"]) && |
|||||
277 | 2 | !empty($this->defaults["controller"]) && |
|||||
278 | 2 | !empty($this->defaults["view"]) |
|||||
279 | ) { |
||||||
280 | 1 | $module = $this->defaults["module"]; |
|||||
281 | 1 | $controller = $this->defaults["controller"]; |
|||||
282 | 1 | $view = $this->defaults["view"]; |
|||||
283 | |||||||
284 | 1 | $match = true; |
|||||
285 | } |
||||||
286 | } |
||||||
287 | |||||||
288 | 6 | if (!$match) { |
|||||
289 | 1 | throw new Exception\RouteNotFoundException("The route has not been matched"); |
|||||
290 | } |
||||||
291 | |||||||
292 | 5 | $fqn_controller = call_user_func($this->classNameBuilder, $module, $controller); |
|||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
|
|||||||
293 | |||||||
294 | 5 | if (class_exists($fqn_controller)) { |
|||||
295 | try { |
||||||
296 | 5 | $this->controller = new $fqn_controller; |
|||||
297 | } catch (Exception\MethodNotFoundException $e) { |
||||||
298 | # change context, in terms of Router MethodNotFoundException or |
||||||
299 | # PrivateMethodExecutionException is a PageNotfoundException |
||||||
300 | throw new Exception\PageNotFoundException($e->getMessage(), $e->getCode(), $e); |
||||||
301 | } catch (Exception\PrivateMethodExecutionException $e) { |
||||||
302 | throw new Exception\PageNotFoundException($e->getMessage(), $e->getCode(), $e); |
||||||
303 | } |
||||||
304 | |||||||
305 | # in controller terms, a view is a method |
||||||
306 | 5 | $this->controller->setMethod($view); |
|||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
307 | } else { |
||||||
308 | throw new Exception\ControllerNotFoundException("The control class '$fqn_controller' does not exists!"); |
||||||
309 | } |
||||||
310 | 5 | } |
|||||
311 | |||||||
312 | /** |
||||||
313 | * Execute the method matched in the controller |
||||||
314 | * |
||||||
315 | * @return mixed |
||||||
316 | */ |
||||||
317 | 2 | public function run() |
|||||
318 | { |
||||||
319 | 2 | return $this->controller->execute(); |
|||||
320 | } |
||||||
321 | |||||||
322 | /** |
||||||
323 | * Adds a new route to router |
||||||
324 | * |
||||||
325 | * @param Array $route |
||||||
326 | * |
||||||
327 | * @throws LogicException |
||||||
328 | * |
||||||
329 | * @return null |
||||||
330 | */ |
||||||
331 | 6 | public function addRoute(array $route) |
|||||
332 | { |
||||||
333 | 6 | $key = array_keys($route); |
|||||
334 | |||||||
335 | 6 | if (count($key) > 1) { |
|||||
336 | throw new \InvalidArgumentException("So many keys in a simple route"); |
||||||
337 | } |
||||||
338 | |||||||
339 | 6 | $key = array_shift($key); |
|||||
340 | |||||||
341 | 6 | $identifiers = ["module", "controller", "view"]; |
|||||
342 | |||||||
343 | 6 | foreach ($identifiers as $identifier) { |
|||||
344 | 6 | if (!array_key_exists($identifier, $route[$key])) { |
|||||
345 | throw new \InvalidArgumentException("The identifier '$identifier' does not exists in the route"); |
||||||
346 | } |
||||||
347 | |||||||
348 | 6 | if (!is_string($route[$key][$identifier])) { |
|||||
349 | 6 | throw new \InvalidArgumentException("Invalid type given for '$identifier'. String expected."); |
|||||
350 | } |
||||||
351 | } |
||||||
352 | |||||||
353 | 6 | if (array_key_exists($key, $this->routes)) { |
|||||
354 | throw new \LogicException("The key '$key' was already defined as a route"); |
||||||
355 | } |
||||||
356 | |||||||
357 | 6 | $this->routes = array_merge($this->routes, $route); |
|||||
358 | 6 | } |
|||||
359 | |||||||
360 | /** |
||||||
361 | * Adds a new route to router |
||||||
362 | * |
||||||
363 | * @param string $name |
||||||
364 | * @param Zend\Router\Http\RouteInterface $route |
||||||
0 ignored issues
–
show
|
|||||||
365 | * |
||||||
366 | * @throws LogicException |
||||||
367 | * |
||||||
368 | * @return null |
||||||
369 | */ |
||||||
370 | public function addZendRoute($name, \Zend\Router\Http\RouteInterface $route) |
||||||
371 | { |
||||||
372 | $this->zendRouter->addRoute($name, $route); |
||||||
373 | } |
||||||
374 | |||||||
375 | /** |
||||||
376 | * Parse key value pairs from a string |
||||||
377 | * |
||||||
378 | * Searches for the pattern /var1/value1/var2/value2 and converts it to |
||||||
379 | * |
||||||
380 | * var1 => value1 |
||||||
381 | * var2 => value2 |
||||||
382 | * |
||||||
383 | * @param string $unparsed |
||||||
384 | * |
||||||
385 | * @return array |
||||||
386 | */ |
||||||
387 | private function parseKeyValuePairsFrom($unparsed) |
||||||
0 ignored issues
–
show
|
|||||||
388 | { |
||||||
389 | $params = explode("/", $unparsed); |
||||||
390 | |||||||
391 | $vars = $values = []; |
||||||
392 | |||||||
393 | $i = 1; |
||||||
394 | foreach ($params as $item) { |
||||||
395 | if ($i % 2 != 0) { |
||||||
396 | $vars[] = $item; |
||||||
397 | } else { |
||||||
398 | $values[] = $item; |
||||||
399 | } |
||||||
400 | $i++; |
||||||
401 | } |
||||||
402 | |||||||
403 | $vars_count = count($vars); |
||||||
404 | |||||||
405 | $result = []; |
||||||
406 | |||||||
407 | for ($i = 0; $i < $vars_count; $i++) { |
||||||
408 | if (array_key_exists($i, $values)) { |
||||||
409 | $result[$vars[$i]] = $values[$i]; |
||||||
410 | } else { |
||||||
411 | $result[$vars[$i]] = ''; |
||||||
412 | } |
||||||
413 | } |
||||||
414 | |||||||
415 | return $result; |
||||||
416 | } |
||||||
417 | } |
||||||
418 |