These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Anax\Route; |
||
4 | |||
5 | use Anax\Commons\ContainerInjectableInterface; |
||
6 | use Anax\Route\Exception\ConfigurationException; |
||
7 | use Anax\Route\Exception\NotFoundException; |
||
8 | use Psr\Container\ContainerInterface; |
||
9 | |||
10 | /** |
||
11 | * Call a routes handler and return the results. |
||
12 | */ |
||
13 | class RouteHandler |
||
14 | { |
||
15 | /** |
||
16 | * @var ContainerInterface $di the dependency/service container. |
||
17 | */ |
||
18 | protected $di; |
||
19 | |||
20 | |||
21 | |||
22 | /** |
||
23 | * Handle the action for a route and return the results. |
||
24 | * |
||
25 | * @param string $method the request method. |
||
26 | * @param string $path that was matched. |
||
27 | * @param string|array $action base for the callable. |
||
28 | * @param array $arguments optional arguments. |
||
29 | * @param ContainerInjectableInterface $di container with services. |
||
30 | * |
||
31 | * @return mixed as the result from the route handler. |
||
32 | */ |
||
33 | 147 | public function handle( |
|
34 | string $method = null, |
||
35 | string $path = null, |
||
36 | $action, |
||
37 | array $arguments = [], |
||
38 | ContainerInterface $di = null |
||
39 | ) { |
||
40 | 147 | $this->di = $di; |
|
41 | |||
42 | 147 | if (is_null($action)) { |
|
43 | 1 | return; |
|
44 | } |
||
45 | |||
46 | 146 | if (is_callable($action)) { |
|
47 | 118 | if (is_array($action) |
|
48 | 118 | && is_string($action[0]) |
|
49 | 118 | && class_exists($action[0]) |
|
50 | ) { |
||
51 | 2 | $action[] = $arguments; |
|
52 | 2 | return $this->handleAsControllerAction($action); |
|
53 | } |
||
54 | 116 | return $this->handleAsCallable($action, $arguments); |
|
55 | } |
||
56 | |||
57 | 28 | if (is_string($action) && class_exists($action)) { |
|
58 | 24 | $callable = $this->isControllerAction($method, $path, $action); |
|
59 | 23 | if ($callable) { |
|
60 | 20 | return $this->handleAsControllerAction($callable); |
|
61 | } |
||
62 | } |
||
63 | |||
64 | 8 | if ($di |
|
65 | 8 | && is_array($action) |
|
66 | 8 | && isset($action[0]) |
|
67 | 8 | && isset($action[1]) |
|
68 | 8 | && is_string($action[0]) |
|
69 | ) { |
||
70 | // Try to load service from app/di injected container |
||
71 | 3 | return $this->handleUsingDi($action, $arguments, $di); |
|
72 | } |
||
73 | |||
74 | 5 | throw new ConfigurationException("Handler for route does not seem to be a callable action."); |
|
75 | } |
||
76 | |||
77 | |||
78 | |||
79 | /** |
||
80 | * Get an informative string representing the handler type. |
||
81 | * |
||
82 | * @param string|array $action base for the callable. |
||
83 | * @param ContainerInjectableInterface $di container with services. |
||
84 | * |
||
85 | * @return string as the type of handler. |
||
86 | */ |
||
87 | 2 | public function getHandlerType( |
|
88 | $action, |
||
89 | ContainerInterface $di = null |
||
90 | ) { |
||
91 | 2 | if (is_null($action)) { |
|
92 | 1 | return "null"; |
|
93 | } |
||
94 | |||
95 | 2 | if (is_callable($action)) { |
|
96 | 1 | return "callable"; |
|
97 | } |
||
98 | |||
99 | 2 | if (is_string($action) && class_exists($action)) { |
|
100 | 1 | $callable = $this->isControllerAction(null, null, $action); |
|
101 | 1 | if ($callable) { |
|
102 | 1 | return "controller"; |
|
103 | } |
||
104 | } |
||
105 | |||
106 | 2 | if ($di |
|
107 | 2 | && is_array($action) |
|
108 | 2 | && isset($action[0]) |
|
109 | 2 | && isset($action[1]) |
|
110 | 2 | && is_string($action[0]) |
|
111 | 2 | && $di->has($action[0]) |
|
112 | 2 | && is_callable([$di->get($action[0]), $action[1]]) |
|
113 | ) { |
||
114 | 1 | return "di"; |
|
115 | } |
||
116 | |||
117 | 1 | return "not found"; |
|
118 | } |
||
119 | |||
120 | |||
121 | |||
122 | /** |
||
123 | * Check if items can be used to call a controller action, verify |
||
124 | * that the controller exists, the action has a class-method to call. |
||
125 | * |
||
126 | * @param string $method the request method. |
||
127 | * @param string $path the matched path, base for the controller action |
||
128 | * and the arguments. |
||
129 | * @param string $class the controller class |
||
130 | * |
||
131 | * @return array with callable details. |
||
132 | */ |
||
133 | 25 | protected function isControllerAction( |
|
134 | string $method = null, |
||
135 | string $path = null, |
||
136 | string $class |
||
137 | ) { |
||
138 | 25 | $method = ucfirst(strtolower($method)); |
|
139 | 25 | $args = explode("/", $path); |
|
140 | 25 | $action = array_shift($args); |
|
141 | 25 | $action = empty($action) ? "index" : $action; |
|
142 | 25 | $action = str_replace("-", "", $action); |
|
143 | 25 | $action1 = "{$action}Action{$method}"; |
|
144 | 25 | $action2 = "{$action}Action"; |
|
145 | 25 | $action3 = "catchAll{$method}"; |
|
146 | 25 | $action4 = "catchAll"; |
|
147 | |||
148 | 25 | View Code Duplication | foreach ([$action1, $action2] as $target) { |
149 | try { |
||
150 | 25 | $refl = new \ReflectionMethod($class, $target); |
|
151 | 18 | if (!$refl->isPublic()) { |
|
152 | 1 | throw new NotFoundException("Controller method '$class::$target' is not a public method."); |
|
153 | } |
||
154 | |||
155 | 17 | return [$class, $target, $args]; |
|
156 | 17 | } catch (\ReflectionException $e) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
Loading history...
|
|||
157 | ; |
||
158 | } |
||
159 | } |
||
160 | |||
161 | 8 | View Code Duplication | foreach ([$action3, $action4] as $target) { |
162 | try { |
||
163 | 8 | $refl = new \ReflectionMethod($class, $target); |
|
164 | 4 | if (!$refl->isPublic()) { |
|
165 | throw new NotFoundException("Controller method '$class::$target' is not a public method."); |
||
166 | } |
||
167 | |||
168 | 4 | array_unshift($args, $action); |
|
169 | 4 | return [$class, $target, $args]; |
|
170 | 5 | } catch (\ReflectionException $e) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
171 | ; |
||
172 | } |
||
173 | } |
||
174 | |||
175 | 4 | return false; |
|
176 | } |
||
177 | |||
178 | |||
179 | |||
180 | /** |
||
181 | * Call the controller action with optional arguments and call |
||
182 | * initialisation methods if available. |
||
183 | * |
||
184 | * @param string $callable with details on what controller action to call. |
||
185 | * |
||
186 | * @return mixed result from the handler. |
||
187 | */ |
||
188 | 22 | protected function handleAsControllerAction(array $callable) |
|
189 | { |
||
190 | 22 | $class = $callable[0]; |
|
191 | 22 | $action = $callable[1]; |
|
192 | 22 | $args = $callable[2]; |
|
193 | 22 | $obj = new $class(); |
|
194 | |||
195 | 22 | $refl = new \ReflectionClass($class); |
|
196 | 22 | $diInterface = "Anax\Commons\ContainerInjectableInterface"; |
|
197 | 22 | $appInterface = "Anax\Commons\AppInjectableInterface"; |
|
198 | |||
199 | 22 | if ($this->di && $refl->implementsInterface($diInterface)) { |
|
200 | 1 | $obj->setDI($this->di); |
|
201 | 21 | } elseif ($this->di && $refl->implementsInterface($appInterface)) { |
|
202 | 2 | if (!$this->di->has("app")) { |
|
203 | 1 | throw new ConfigurationException( |
|
204 | 1 | "Controller '$class' implements AppInjectableInterface but \$app is not available in \$di." |
|
205 | ); |
||
206 | } |
||
207 | 1 | $obj->setApp($this->di->get("app")); |
|
208 | } |
||
209 | |||
210 | try { |
||
211 | 21 | $refl = new \ReflectionMethod($class, "initialize"); |
|
212 | 14 | if ($refl->isPublic()) { |
|
213 | 14 | $obj->initialize(); |
|
214 | } |
||
215 | 7 | } catch (\ReflectionException $e) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
216 | ; |
||
217 | } |
||
218 | |||
219 | 21 | $refl = new \ReflectionMethod($obj, $action); |
|
220 | 21 | $paramIsVariadic = false; |
|
221 | 21 | foreach ($refl->getParameters() as $param) { |
|
222 | 9 | if ($param->isVariadic()) { |
|
223 | 4 | $paramIsVariadic = true; |
|
224 | 9 | break; |
|
225 | } |
||
226 | } |
||
227 | |||
228 | 21 | if (!$paramIsVariadic |
|
229 | 21 | && $refl->getNumberOfParameters() < count($args) |
|
230 | ) { |
||
231 | 1 | throw new NotFoundException( |
|
232 | 1 | "Controller '$class' with action method '$action' valid but to many parameters. Got " |
|
233 | 1 | . count($args) |
|
234 | 1 | . ", expected " |
|
235 | 1 | . $refl->getNumberOfParameters() . "." |
|
236 | ); |
||
237 | } |
||
238 | |||
239 | try { |
||
240 | 20 | $res = $obj->$action(...$args); |
|
241 | 2 | } catch (\ArgumentCountError $e) { |
|
242 | 1 | throw new NotFoundException($e->getMessage()); |
|
243 | 1 | } catch (\TypeError $e) { |
|
244 | 1 | throw new NotFoundException($e->getMessage()); |
|
245 | } |
||
246 | |||
247 | 18 | return $res; |
|
248 | } |
||
249 | |||
250 | |||
251 | |||
252 | /** |
||
253 | * Handle as callable support callables where the method is not static. |
||
254 | * |
||
255 | * @param string|array $action base for the callable |
||
256 | * @param array $arguments optional arguments |
||
257 | * @param ContainerInjectableInterface $di container with services |
||
258 | * |
||
259 | * @return mixed as the result from the route handler. |
||
260 | */ |
||
261 | 116 | protected function handleAsCallable( |
|
262 | $action, |
||
263 | array $arguments |
||
264 | ) { |
||
265 | 116 | if (is_array($action) |
|
266 | 116 | && isset($action[0]) |
|
267 | 116 | && isset($action[1]) |
|
268 | 116 | && is_string($action[0]) |
|
269 | 116 | && is_string($action[1]) |
|
270 | 116 | && class_exists($action[0]) |
|
271 | ) { |
||
272 | // ["SomeClass", "someMethod"] but not static |
||
273 | $refl = new \ReflectionMethod($action[0], $action[1]); |
||
274 | if ($refl->isPublic() && !$refl->isStatic()) { |
||
275 | $obj = new $action[0](); |
||
276 | return $obj->{$action[1]}(...$arguments); |
||
277 | } |
||
278 | } |
||
279 | |||
280 | // Add $di to param list, if defined by the callback |
||
281 | 116 | $refl = is_array($action) |
|
282 | 1 | ? new \ReflectionMethod($action[0], $action[1]) |
|
283 | 116 | : new \ReflectionFunction($action); |
|
284 | 116 | $params = $refl->getParameters(); |
|
285 | 116 | if (isset($params[0]) && $params[0]->getName() === "di") { |
|
286 | 1 | array_unshift($arguments, $this->di); |
|
287 | } |
||
288 | |||
289 | 116 | return call_user_func($action, ...$arguments); |
|
290 | } |
||
291 | |||
292 | |||
293 | |||
294 | /** |
||
295 | * Load callable as a service from the $di container. |
||
296 | * |
||
297 | * @param string|array $action base for the callable |
||
298 | * @param array $arguments optional arguments |
||
299 | * @param ContainerInjectableInterface $di container with services |
||
300 | * |
||
301 | * @return mixed as the result from the route handler. |
||
302 | */ |
||
303 | 3 | protected function handleUsingDi( |
|
304 | $action, |
||
305 | array $arguments, |
||
306 | ContainerInterface $di |
||
307 | ) { |
||
308 | 3 | if (!$di->has($action[0])) { |
|
309 | 1 | throw new ConfigurationException("Routehandler '{$action[0]}' not loaded in di."); |
|
310 | } |
||
311 | |||
312 | 2 | $service = $di->get($action[0]); |
|
313 | 2 | if (!is_callable([$service, $action[1]])) { |
|
314 | 1 | throw new ConfigurationException( |
|
315 | 1 | "Routehandler '{$action[0]}' does not have a callable method '{$action[1]}'." |
|
316 | ); |
||
317 | } |
||
318 | |||
319 | 1 | return call_user_func( |
|
320 | 1 | [$service, $action[1]], |
|
321 | 1 | ...$arguments |
|
322 | ); |
||
323 | } |
||
324 | } |
||
325 |