Completed
Push — master ( 8a28dc...244128 )
by Mikael
02:20
created

RouteHandler::getHandlerType()   C

Complexity

Conditions 13
Paths 7

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 13

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 18
cts 18
cp 1
rs 6.6166
c 0
b 0
f 0
cc 13
nc 7
nop 2
crap 13

How to fix   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
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 141
    public function handle(
34
        string $method = null,
35
        string $path = null,
36
        $action,
37
        array $arguments = [],
38
        ContainerInterface $di = null
39
    ) {
40 141
        $this->di = $di;
41
42 141
        if (is_null($action)) {
43 1
            return;
44
        }
45
46 140
        if (is_callable($action)) {
47 117
            return $this->handleAsCallable($action, $arguments);
48
        }
49
50 23
        if (is_string($action) && class_exists($action)) {
51 19
            $callable = $this->isControllerAction($method, $path, $action);
52 18
            if ($callable) {
53 17
                return $this->handleAsControllerAction($callable);
54
            }
55
        }
56
57 5
        if ($di
58 5
            && is_array($action)
59 5
            && isset($action[0])
60 5
            && isset($action[1])
61 5
            && is_string($action[0])
62
        ) {
63
            // Try to load service from app/di injected container
64 3
            return $this->handleUsingDi($action, $arguments, $di);
65
        }
66
        
67 2
        throw new ConfigurationException("Handler for route does not seem to be a callable action.");
68
    }
69
70
71
72
    /**
73
     * Get  an informative string representing the handler type.
74
     *
75
     * @param string|array                 $action    base for the callable.
76
     * @param ContainerInjectableInterface $di        container with services.
77
     *
78
     * @return string as the type of handler.
79
     */
80 2
    public function getHandlerType(
81
        $action,
82
        ContainerInterface $di = null
83
    ) {
84 2
        if (is_null($action)) {
85 1
            return "null";
86
        }
87
88 2
        if (is_callable($action)) {
89 1
            return "callable";
90
        }
91
92 2
        if (is_string($action) && class_exists($action)) {
93 1
            $callable = $this->isControllerAction(null, null, $action);
94 1
            if ($callable) {
95 1
                return "controller";
96
            }
97
        }
98
99 2
        if ($di
100 2
            && is_array($action)
101 2
            && isset($action[0])
102 2
            && isset($action[1])
103 2
            && is_string($action[0])
104 2
            && $di->has($action[0])
105 2
            && is_callable([$di->get($action[0]), $action[1]])
106
        ) {
107 1
            return "di";
108
        }
109
110 1
        return "not found";
111
    }
112
113
114
115
    /**
116
     * Check if items can be used to call a controller action, verify
117
     * that the controller exists, the action has a class-method to call.
118
     *
119
     * @param string $method the request method.
120
     * @param string $path   the matched path, base for the controller action
121
     *                       and the arguments.
122
     * @param string $class  the controller class
123
     *
124
     * @return array with callable details.
125
     */
126 20
    protected function isControllerAction(
127
        string $method = null,
128
        string $path = null,
129
        string $class
130
    ) {
131 20
        $args = explode("/", $path);
132 20
        $action = array_shift($args);
133 20
        $action = empty($action) ? "index" : $action;
134 20
        $action = str_replace("-", "", $action);
135 20
        $action1 = "{$action}Action" . ucfirst(strtolower($method));
136 20
        $action2 = "{$action}Action";
137 20
        $action3 = "catchAll";
138
139 20
        $refl = null;
140 20
        foreach ([$action1, $action2, $action3] as $action) {
141
            try {
142 20
                $refl = new \ReflectionMethod($class, $action);
143 19
                if (!$refl->isPublic()) {
144 1
                    throw new NotFoundException("Controller method '$class::$action' is not a public method.");
145
                }
146
147 18
                return [$class, $action, $args];
148 13
            } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
149
                ;
150
            }
151
        }
152
153 1
        return false;
154
    }
155
156
157
158
    /**
159
     * Call the controller action with optional arguments and call
160
     * initialisation methods if available.
161
     *
162
     * @param string $callable with details on what controller action to call.
163
     *
164
     * @return mixed result from the handler.
165
     */
166 17
    protected function handleAsControllerAction(array $callable)
167
    {
168 17
        $class = $callable[0];
169 17
        $action = $callable[1];
170 17
        $args = $callable[2];
171 17
        $obj = new $class();
172
173 17
        $refl = new \ReflectionClass($class);
174 17
        $diInterface = "Anax\Commons\ContainerInjectableInterface";
175 17
        $appInterface = "Anax\Commons\AppInjectableInterface";
176
177 17
        if ($this->di && $refl->implementsInterface($diInterface)) {
178 1
            $obj->setDI($this->di);
179 16
        } elseif ($this->di && $refl->implementsInterface($appInterface)) {
180 2
            if (!$this->di->has("app")) {
181 1
                throw new ConfigurationException(
182 1
                    "Controller '$class' implements AppInjectableInterface but \$app is not available in \$di."
183
                );
184
            }
185 1
            $obj->setApp($this->di->get("app"));
186
        }
187
188
        try {
189 16
            $refl = new \ReflectionMethod($class, "initialize");
190 12
            if ($refl->isPublic()) {
191 12
                $obj->initialize();
192
            }
193 4
        } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
194
            ;
195
        }
196
197 16
        $refl = new \ReflectionMethod($obj, $action);
198 16
        $paramIsVariadic = false;
199 16
        foreach ($refl->getParameters() as $param) {
200 8
            if ($param->isVariadic()) {
201 3
                $paramIsVariadic = true;
202 8
                break;
203
            }
204
        }
205
206 16
        if (!$paramIsVariadic
207 16
            && $refl->getNumberOfParameters() < count($args)
208
        ) {
209 1
            throw new NotFoundException(
210 1
                "Controller '$class' with action method '$action' valid but to many parameters. Got "
211 1
                . count($args)
212 1
                . ", expected "
213 1
                . $refl->getNumberOfParameters() . "."
214
            );
215
        }
216
217
        try {
218 15
            $res = $obj->$action(...$args);
219 2
        } catch (\ArgumentCountError $e) {
0 ignored issues
show
Bug introduced by
The class ArgumentCountError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
220 1
            throw new NotFoundException($e->getMessage());
221 1
        } catch (\TypeError $e) {
222 1
            throw new NotFoundException($e->getMessage());
223
        }
224
225 13
        return $res;
226
    }
227
228
229
230
    /**
231
     * Handle as callable support callables where the method is not static.
232
     *
233
     * @param string|array                 $action    base for the callable
234
     * @param array                        $arguments optional arguments
235
     * @param ContainerInjectableInterface $di        container with services
0 ignored issues
show
Bug introduced by
There is no parameter named $di. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
236
     *
237
     * @return mixed as the result from the route handler.
238
     */
239 117
    protected function handleAsCallable(
240
        $action,
241
        array $arguments
242
    ) {
243 117
        if (is_array($action)
244 117
            && isset($action[0])
245 117
            && isset($action[1])
246 117
            && is_string($action[0])
247 117
            && is_string($action[1])
248 117
            && class_exists($action[0])
249
        ) {
250
            // ["SomeClass", "someMethod"] but not static
251 2
            $refl = new \ReflectionMethod($action[0], $action[1]);
252 2
            if ($refl->isPublic() && !$refl->isStatic()) {
253 1
                $obj = new $action[0]();
254 1
                return $obj->{$action[1]}(...$arguments);
255
            }
256
        }
257
258
        // Add $di to param list, if defined by the callback
259 116
        $refl = is_array($action)
260 2
            ? new \ReflectionMethod($action[0], $action[1])
261 116
            : new \ReflectionFunction($action);
262 116
        $params = $refl->getParameters();
263 116
        if (isset($params[0]) && $params[0]->getName() === "di") {
264 1
            array_unshift($arguments, $this->di);
265
        }
266
267 116
        return call_user_func($action, ...$arguments);
268
    }
269
270
271
272
    /**
273
     * Load callable as a service from the $di container.
274
     *
275
     * @param string|array                 $action    base for the callable
276
     * @param array                        $arguments optional arguments
277
     * @param ContainerInjectableInterface $di        container with services
278
     *
279
     * @return mixed as the result from the route handler.
280
     */
281 3
    protected function handleUsingDi(
282
        $action,
283
        array $arguments,
284
        ContainerInterface $di
285
    ) {
286 3
        if (!$di->has($action[0])) {
287 1
            throw new ConfigurationException("Routehandler '{$action[0]}' not loaded in di.");
288
        }
289
    
290 2
        $service = $di->get($action[0]);
291 2
        if (!is_callable([$service, $action[1]])) {
292 1
            throw new ConfigurationException(
293 1
                "Routehandler '{$action[0]}' does not have a callable method '{$action[1]}'."
294
            );
295
        }
296
    
297 1
        return call_user_func(
298 1
            [$service, $action[1]],
299 1
            ...$arguments
300
        );
301
    }
302
}
303