Passed
Branch master (32ad63)
by Bohuslav
02:06
created

ClassAutoBind::getMethodParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 2
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Kambo\Router\Dispatcher;
5
6
// \Spl
7
use ReflectionMethod;
8
9
// \Kambo\Router
10
use Kambo\Router\Dispatcher;
11
use Kambo\Router\Route\Route\Parsed;
12
13
/**
14
 * Class dispatcher with module/controller/action support
15
 *
16
 * @package Kambo\Router\Dispatcher
17
 * @author  Bohuslav Simek <[email protected]>
18
 * @license MIT
19
 */
20
class ClassAutoBind implements Dispatcher
21
{
22
    /**
23
     * Not found handler which will be called if nothing has been found.
24
     *
25
     * @var mixed
26
     */
27
    private $notFoundHandler;
28
29
    /**
30
     * Base namespace for all dispatched classes.
31
     *
32
     * @var string
33
     */
34
    private $baseNamespace = null;
35
36
    /**
37
     * Name of class that will be used for constructing a namespace for proper
38
     * class resolve.
39
     *
40
     * @var string
41
     */
42
    private $controllerName = 'Controllers';
43
44
    /**
45
     * Name of module that will be used for constructing a namespace for proper
46
     * class resolve.
47
     *
48
     * @var string
49
     */
50
    private $moduleName = 'Modules';
51
52
    /**
53
     * Prefix for action method.
54
     * Target method is allways called with this prefix.
55
     *
56
     * @var string
57
     */
58
    private $actionName = 'action';
59
60
    /**
61
     * Dispatch found route with given parameters
62
     *
63
     * @param \Kambo\Router\Route\Route\Parsed $route      Instance of found and parsed route.
64
     * @param array                            $parameters Additional parameters.
65
     *
66
     * @return mixed
67
     */
68 11
    public function dispatchRoute(Parsed $route, array $parameters = [])
69
    {
70 11
        $handler = $route->getHandler();
71 11
        if (isset($handler['controler']) && isset($handler['action'])) {
72 10
            $routePlaceholders             = $route->getPlaceholders();
73 10
            list($controllerName, $action) = $this->resolveControlerAction(
74 10
                $route->getParameters(),
75 10
                $routePlaceholders,
76 10
                $handler
77
            );
78
79
            // Create instance of target class
80 10
            $controller = new $controllerName();
81
            // Create class method name with prefix
82 10
            $methodName = $this->actionName.$action;
83
84 10
            $parameterMap = $this->getMethodParameters(
85 10
                $controllerName,
86 10
                $methodName
87
            );
88
89 10
            $methodParameters = $this->getFunctionArgumentsControlers(
90 10
                $parameterMap,
91 10
                $route->getParameters(),
92 10
                $routePlaceholders,
93 10
                $handler
94
            );
95
96 10
            return call_user_func_array(
97
                [
98 10
                    $controller,
99 10
                    $methodName
100
                ],
101 10
                $methodParameters
102
            );
103
        }
104
105 1
        return $this->dispatchNotFound();
106
    }
107
108
    /**
109
     * Called if any of route did not match the request.
110
     *
111
     * @return mixed
112
     */
113 3
    public function dispatchNotFound()
114
    {
115 3
        if (isset($this->notFoundHandler)) {
116 2
            $notFoundHandler = $this->notFoundHandler;
117 2
            $controllerName  = implode(
118 2
                '\\',
119
                [
120 2
                    $this->baseNamespace,
121 2
                    $this->controllerName,
122 2
                    $notFoundHandler['controler']
123
                ]
124
            );
125
126 2
            $controllerInstance = new $controllerName();
127
128 2
            return call_user_func(
129
                [
130 2
                    $controllerInstance,
131 2
                    $this->actionName.$notFoundHandler['action']
132
                ]
133
            );
134
        }
135
136 1
        return null;
137
    }
138
139
    /**
140
     * Set base namespace to allow proper resolve of class name
141
     *
142
     * @param string $baseNamespace base namespace
143
     *
144
     * @return self for fluent interface
145
     */
146 25
    public function setBaseNamespace(string $baseNamespace)
147
    {
148 25
        $this->baseNamespace = $baseNamespace;
149
150 25
        return $this;
151
    }
152
153
    /**
154
     * Sets not found handler
155
     *
156
     * @param mixed $handler handler that will be excuted if nothing has been
157
     *                        found
158
     *
159
     * @return self for fluent interface
160
     */
161 2
    public function setNotFoundHandler($handler)
162
    {
163 2
        $this->notFoundHandler = $handler;
164
165 2
        return $this;
166
    }
167
168
    // ------------ PRIVATE METHODS
169
170
    /**
171
     * Resolve target name of class (controller) and method (action)
172
     *
173
     * @param mixed $matches    found matched variables
174
     * @param mixed $parameters route parameters
175
     * @param mixed $handler    handler that should be executed
176
     *
177
     * @return mixed
178
     */
179 10
    private function resolveControlerAction($matches, $parameters, $handler)
180
    {
181 10
        $controler = $handler['controler'];
182 10
        $action    = $handler['action'];
183 10
        $namespace = $this->resolveNamespace($parameters, $handler, $matches);
184
185 10
        if ($this->isPlaceholder($action)) {
186 9
            $transformed = $this->transformHandler($matches, $parameters, $handler);
187 9
            if ($this->isPlaceholder($controler)) {
188 7
                $controler = $namespace.'\\'.$transformed['controler'];
189
            } else {
190 2
                $controler = $namespace.'\\'.$controler;
191
            }
192
193
            return [
194 9
                $controler,
195 9
                $transformed['action']
196
            ];
197
        }
198
199
        return [
200 1
            $namespace.'\\'.$controler,
201 1
            $action
202
        ];
203
    }
204
205
    /**
206
     * Transform provided handler with variables and parameters
207
     *
208
     * @param mixed $matches    found matched variables
209
     * @param mixed $parameters route parameters
210
     * @param mixed $handler    handler that should be executed
211
     *
212
     * @return mixed
213
     */
214 9
    private function transformHandler($matches, $parameters, $handler)
215
    {
216 9
        $transformed = [];
217 9
        foreach ($handler as $target => $placeholder) {
218 9
            foreach ($parameters as $key => $parameterName) {
219 9
                if ($parameterName[0][0] == $placeholder) {
220 9
                    if ($target == 'controler') {
221 7
                        $transformed[$target] = $matches[$key].'Controler';
222
                    } else {
223 9
                        $transformed[$target] = $matches[$key];
224
                    }
225
                }
226
            }
227
        }
228
229 9
        return $transformed;
230
    }
231
232
    /**
233
     * Resolve proper namespace according parameters, handler and matches
234
     *
235
     * @param mixed $parameters route parameters
236
     * @param mixed $handler    handler that should be executed
237
     * @param mixed $matches    found matched variables
238
239
     * @return mixed
240
     */
241 10
    private function resolveNamespace($parameters, $handler, $matches)
242
    {
243 10
        if (isset($handler['module'])) {
244 5
            $moduleName = $handler['module'];
245 5
            if ($this->isPlaceholder($moduleName)) {
246 4
                foreach ($handler as $target => $placeholder) {
247 4
                    foreach ($parameters as $key => $parameterName) {
248 4
                        if ($parameterName[0][0] == $placeholder) {
249 4
                            if ($target == 'module') {
250 4
                                $moduleName = $matches[$key];
251
                            }
252
                        }
253
                    }
254
                }
255
            }
256
257 5
            return implode(
258 5
                '\\',
259
                [
260 5
                    $this->baseNamespace,
261 5
                    $this->moduleName,
262 5
                    $moduleName,
263 5
                    $this->controllerName
264
                ]
265
            );
266
        }
267
268 5
        return $this->baseNamespace.'\\'.$this->controllerName;
269
    }
270
271
    /**
272
     * Check if the variable is placeholder
273
     *
274
     * @param string $value found route
275
     *
276
     * @return boolean true if value should be transfered
277
     */
278 10
    private function isPlaceholder(string $value) : bool
279
    {
280 10
        if (strrchr($value, '}') && (0 === strpos($value, '{'))) {
281 9
            return true;
282
        }
283
284 4
        return false;
285
    }
286
287
    /**
288
     * Get function arguments for controler
289
     *
290
     * @param mixed $paramMap   parameter map
291
     * @param mixed $matches    found matched variables
292
     * @param mixed $parameters route parameters
293
     * @param mixed $handlers   handler that should be executed
294
295
     * @return mixed
296
     */
297 10
    private function getFunctionArgumentsControlers(
298
        $paramMap,
299
        $matches,
300
        $parameters,
301
        $handlers
302
    ) {
303 10
        $output  = [];
304 10
        $matches = array_values($matches);
305
306 10
        if (isset($parameters)) {
307 10
            foreach ($handlers as $placeholder) {
308 10
                if ($this->isPlaceholder($placeholder)) {
309 9
                    foreach ($parameters as $key => $parameterName) {
310 9
                        if ($parameterName[0][0] == $placeholder) {
311 9
                            unset($parameters[$key]);
312 10
                            unset($matches[$key]);
313
                        }
314
                    }
315
                }
316
            }
317
318 10
            $parameters = array_values($parameters);
319 10
            $matches    = array_values($matches);
320
321 10 View Code Duplication
            foreach ($parameters as $key => $valueName) {
322 10
                foreach ($paramMap as $possition => $value) {
323 10
                    if ($value == $valueName[1][0]) {
324 10
                        $output[] = $matches[$possition];
325
                    }
326
                }
327
            }
328
        }
329
330 10
        return $output;
331
    }
332
333
    /**
334
     * Get names of parameters for provided class and method
335
     *
336
     * @param string $class      name of class
337
     * @param string $methodName name of method
338
     *
339
     * @return array
340
     */
341 10
    private function getMethodParameters(string $class, string $methodName) : array
342
    {
343 10
        $methodReflection = new ReflectionMethod($class, $methodName);
344 10
        $parametersName   = [];
345
346 10
        foreach ($methodReflection->getParameters() as $parameter) {
347 10
            $parametersName[] = $parameter->name;
348
        }
349
350 10
        return $parametersName;
351
    }
352
}
353