Completed
Push — master ( 969b43...8d77fc )
by Mikael
04:27
created

RouteHandler   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 3
dl 0
loc 259
ccs 96
cts 96
cp 1
rs 7.92
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
B handle() 0 36 11
A isControllerAction() 0 28 5
B handleAsControllerAction() 0 31 7
C handleAsCallable() 0 30 12
A handleUsingDi() 0 21 3
C getHandlerType() 0 32 13

How to fix   Complexity   

Complex Class

Complex classes like RouteHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RouteHandler, and based on these observations, apply Extract Interface, too.

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 143
    public function handle(
34
        string $method = null,
35
        string $path = null,
36
        $action,
37
        array $arguments = [],
38
        ContainerInterface $di = null
39
    ) {
40 143
        $this->di = $di;
41
42 143
        if (is_null($action)) {
43 1
            return;
44
        }
45
46 142
        if (is_callable($action)) {
47 117
            return $this->handleAsCallable($action, $arguments);
48
        }
49
50 25
        if (is_string($action) && class_exists($action)) {
51 21
            $callable = $this->isControllerAction($method, $path, $action);
52 20
            if ($callable) {
53 19
                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 22
    protected function isControllerAction(
127
        string $method = null,
128
        string $path = null,
129
        string $class
130
    ) {
131 22
        $args = explode("/", $path);
132 22
        $action = array_shift($args);
133 22
        $action = empty($action) ? "index" : $action;
134 22
        $action1 = "{$action}Action{$method}";
135 22
        $action2 = "{$action}Action";
136 22
        $action3 = "catchAll";
137
138 22
        $refl = null;
139 22
        foreach ([$action1, $action2, $action3] as $action) {
140
            try {
141 22
                $refl = new \ReflectionMethod($class, $action);
142 21
                if (!$refl->isPublic()) {
143 1
                    throw new NotFoundException("Controller method '$class::$action' is not a public method.");
144
                }
145
146 20
                return [$class, $action, $args];
147 19
            } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
148
                ;
149
            }
150
        }
151
152 1
        return false;
153
    }
154
155
156
157
    /**
158
     * Call the controller action with optional arguments and call
159
     * initialisation methods if available.
160
     *
161
     * @param string $callable with details on what controller action to call.
162
     *
163
     * @return mixed result from the handler.
164
     */
165 19
    protected function handleAsControllerAction(array $callable)
166
    {
167 19
        $class = $callable[0];
168 19
        $action = $callable[1];
169 19
        $args = $callable[2];
170 19
        $obj = new $class();
171
172 19
        $refl = new \ReflectionClass($class);
173 19
        if ($this->di && $refl->implementsInterface("Anax\Commons\ContainerInjectableInterface")) {
174 1
            $obj->setDI($this->di);
175
        }
176
177
        try {
178 19
            $refl = new \ReflectionMethod($class, "initialize");
179 17
            if ($refl->isPublic()) {
180 17
                $obj->initialize();
181
            }
182 2
        } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
183
            ;
184
        }
185
186
        try {
187 19
            $res = $obj->$action(...$args);
188 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...
189 1
            throw new NotFoundException($e->getMessage());
190 1
        } catch (\TypeError $e) {
191 1
            throw new NotFoundException($e->getMessage());
192
        }
193
194 17
        return $res;
195
    }
196
197
198
199
    /**
200
     * Handle as callable support callables where the method is not static.
201
     *
202
     * @param string|array                 $action    base for the callable
203
     * @param array                        $arguments optional arguments
204
     * @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...
205
     *
206
     * @return mixed as the result from the route handler.
207
     */
208 117
    protected function handleAsCallable(
209
        $action,
210
        array $arguments
211
    ) {
212 117
        if (is_array($action)
213 117
            && isset($action[0])
214 117
            && isset($action[1])
215 117
            && is_string($action[0])
216 117
            && is_string($action[1])
217 117
            && class_exists($action[0])
218
        ) {
219
            // ["SomeClass", "someMethod"] but not static
220 2
            $refl = new \ReflectionMethod($action[0], $action[1]);
221 2
            if ($refl->isPublic() && !$refl->isStatic()) {
222 1
                $obj = new $action[0]();
223 1
                return $obj->{$action[1]}(...$arguments);
224
            }
225
        }
226
227
        // Add $di to param list, if defined by the callback
228 116
        $refl = is_array($action)
229 2
            ? new \ReflectionMethod($action[0], $action[1])
230 116
            : new \ReflectionFunction($action);
231 116
        $params = $refl->getParameters();
232 116
        if (isset($params[0]) && $params[0]->getName() === "di") {
233 1
            array_unshift($arguments, $this->di);
234
        }
235
236 116
        return call_user_func($action, ...$arguments);
237
    }
238
239
240
241
    /**
242
     * Load callable as a service from the $di container.
243
     *
244
     * @param string|array                 $action    base for the callable
245
     * @param array                        $arguments optional arguments
246
     * @param ContainerInjectableInterface $di        container with services
247
     *
248
     * @return mixed as the result from the route handler.
249
     */
250 3
    protected function handleUsingDi(
251
        $action,
252
        array $arguments,
253
        ContainerInterface $di
254
    ) {
255 3
        if (!$di->has($action[0])) {
256 1
            throw new ConfigurationException("Routehandler '{$action[0]}' not loaded in di.");
257
        }
258
    
259 2
        $service = $di->get($action[0]);
260 2
        if (!is_callable([$service, $action[1]])) {
261 1
            throw new ConfigurationException(
262 1
                "Routehandler '{$action[0]}' does not have a callable method '{$action[1]}'."
263
            );
264
        }
265
    
266 1
        return call_user_func(
267 1
            [$service, $action[1]],
268 1
            ...$arguments
269
        );
270
    }
271
}
272