Test Failed
Push — master ( 976b6b...f8fb73 )
by Mikael
02:00
created

RouteHandler   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 3
dl 0
loc 216
ccs 0
cts 78
cp 0
rs 9.36
c 0
b 0
f 0

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