Completed
Push — master ( 41c9a2...969b43 )
by Mikael
01:54
created

RouteHandler   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 81.25%

Importance

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

6 Methods

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

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 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 116
            return $this->handleAsCallable($action, $arguments);
48
        }
49
50 24
        if (is_string($action) && class_exists($action)) {
51 20
            $callable = $this->isControllerAction($method, $path, $action);
52 19
            if ($callable) {
53 18
                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
    public function getHandlerType(
81
        $action,
82
        ContainerInterface $di = null
83
    ) {
84
        if (is_null($action)) {
85
            return "null";
86
        }
87
88
        if (is_callable($action)) {
89
            return "callable";
90
        }
91
92
        if (is_string($action) && class_exists($action)) {
93
            $callable = $this->isControllerAction(null, null, $action);
94
            if ($callable) {
95
                return "controller";
96
            }
97
        }
98
99
        if ($di
100
            && is_array($action)
101
            && isset($action[0])
102
            && isset($action[1])
103
            && is_string($action[0])
104
            && $di->has($action[0])
105
            && is_callable([$di->get($action[0]), $action[1]])
106
        ) {
107
            return "di";
108
        }
109
110
        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
        $action1 = "{$action}Action{$method}";
135 20
        $action2 = "{$action}Action";
136 20
        $action3 = "catchAll";
137
138 20
        $refl = null;
139 20
        foreach ([$action1, $action2, $action3] as $action) {
140
            try {
141 20
                $refl = new \ReflectionMethod($class, $action);
142 19
                if (!$refl->isPublic()) {
143 1
                    throw new NotFoundException("Controller method '$class::$action' is not a public method.");
144
                }
145
146 18
                return [$class, $action, $args];
147 17
            } 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 18
    protected function handleAsControllerAction(array $callable)
166
    {
167 18
        $class = $callable[0];
168 18
        $action = $callable[1];
169 18
        $args = $callable[2];
170 18
        $obj = new $class();
171
172 18
        $refl = new \ReflectionClass($class);
173 18
        if ($this->di && $refl->implementsInterface("Anax\Commons\ContainerInjectableInterface")) {
174 1
            $obj->setDI($this->di);
175
        }
176
177
        try {
178 18
            $refl = new \ReflectionMethod($class, "initialize");
179 17
            if ($refl->isPublic()) {
180 17
                $obj->initialize();
181
            }
182 1
        } 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 18
            $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 16
        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 116
    protected function handleAsCallable(
209
        $action,
210
        array $arguments
211
    ) {
212 116
        if (is_array($action)
213 116
            && isset($action[0])
214 116
            && isset($action[1])
215 116
            && is_string($action[0])
216 116
            && is_string($action[1])
217 116
            && 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 115
        $refl = is_array($action)
229 2
            ? new \ReflectionMethod($action[0], $action[1])
230 115
            : new \ReflectionFunction($action);
231 115
        $params = $refl->getParameters();
232 115
        if (isset($params[0]) && $params[0]->getName() === "di") {
233 1
            array_unshift($arguments, $this->di);
234
        }
235
236 115
        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