Completed
Push — master ( 11b479...0fd37c )
by Mikael
02:25
created

RouteHandler::handleAsControllerAction()   B

Complexity

Conditions 7
Paths 30

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 18
cts 18
cp 1
rs 8.4906
c 0
b 0
f 0
cc 7
nc 30
nop 1
crap 7
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 121
    public function handle(
34
        string $method = null,
35
        string $path = null,
36
        $action,
37
        array $arguments = [],
38
        ContainerInterface $di = null
39
    ) {
40 121
        $this->di = $di;
41
42 121
        if (is_null($action)) {
43 1
            return;
44
        }
45
46 120
        if (is_callable($action)) {
47 96
            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
     * 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 20
    protected function isControllerAction(
84
        string $method = null,
85
        string $path = null,
86
        string $class
87
    ) {
88 20
        $args = explode("/", $path);
89 20
        $action = array_shift($args);
90 20
        $action = empty($action) ? "index" : $action;
91 20
        $action1 = "{$action}Action{$method}";
92 20
        $action2 = "{$action}Action";
93 20
        $action3 = "catchAll";
94
95 20
        $refl = null;
96 20
        foreach ([$action1, $action2, $action3] as $action) {
97
            try {
98 20
                $refl = new \ReflectionMethod($class, $action);
99 19
                if (!$refl->isPublic()) {
100 1
                    throw new NotFoundException("Controller method '$class::$action' is not a public method.");
101
                }
102
103 18
                return [$class, $action, $args];
104 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...
105
                ;
106
            }
107
        }
108
109 1
        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 18
    protected function handleAsControllerAction(array $callable)
123
    {
124 18
        $class = $callable[0];
125 18
        $action = $callable[1];
126 18
        $args = $callable[2];
127 18
        $obj = new $class();
128
129 18
        $refl = new \ReflectionClass($class);
130 18
        if ($this->di && $refl->implementsInterface("Anax\Commons\ContainerInjectableInterface")) {
131 1
            $obj->setDI($this->di);
132
        }
133
134
        try {
135 18
            $refl = new \ReflectionMethod($class, "initialize");
136 17
            if ($refl->isPublic()) {
137 17
                $obj->initialize();
138
            }
139 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...
140
            ;
141
        }
142
143
        try {
144 18
            $res = $obj->$action(...$args);
145 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...
146 1
            throw new NotFoundException($e->getMessage());
147 1
        } catch (\TypeError $e) {
148 1
            throw new NotFoundException($e->getMessage());
149
        }
150
151 16
        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 96
    protected function handleAsCallable(
166
        $action,
167
        array $arguments
168
    ) {
169 96
        if (is_array($action)
170 96
            && isset($action[0])
171 96
            && isset($action[1])
172 96
            && is_string($action[0])
173 96
            && is_string($action[1])
174 96
            && class_exists($action[0])
175
        ) {
176
            // ["SomeClass", "someMethod"] but not static
177 2
            $refl = new \ReflectionMethod($action[0], $action[1]);
178 2
            if ($refl->isPublic() && !$refl->isStatic()) {
179 1
                $obj = new $action[0]();
180 1
                return $obj->{$action[1]}(...$arguments);
181
            }
182
        }
183
184
        // Add $di to param list, if defined by the callback
185 95
        $refl = is_array($action)
186 2
            ? new \ReflectionMethod($action[0], $action[1])
187 95
            : new \ReflectionFunction($action);
188 95
        $params = $refl->getParameters();
189 95
        if (isset($params[0]) && $params[0]->getName() === "di") {
190 1
            array_unshift($arguments, $this->di);
191
        }
192
193 95
        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 3
    protected function handleUsingDi(
208
        $action,
209
        array $arguments,
210
        ContainerInterface $di
211
    ) {
212 3
        if (!$di->has($action[0])) {
213 1
            throw new ConfigurationException("Routehandler '{$action[0]}' not loaded in di.");
214
        }
215
    
216 2
        $service = $di->get($action[0]);
217 2
        if (!is_callable([$service, $action[1]])) {
218 1
            throw new ConfigurationException(
219 1
                "Routehandler '{$action[0]}' does not have a callable method '{$action[1]}'."
220
            );
221
        }
222
    
223 1
        return call_user_func(
224 1
            [$service, $action[1]],
225 1
            ...$arguments
226
        );
227
    }
228
}
229