Completed
Pull Request — master (#1)
by Bohuslav
02:31
created

ClassAutoBind::resolveControlerAction()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 0
cts 13
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 16
nc 3
nop 3
crap 12
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
    public function dispatchRoute(Parsed $route, array $parameters = [])
69
    {
70
        $handler = $route->getHandler();
71
        if (isset($handler['controler']) && isset($handler['action'])) {
72
            $routePlaceholders             = $route->getPlaceholders();
73
            list($controllerName, $action) = $this->resolveControlerAction(
74
                $route->getParameters(),
75
                $routePlaceholders,
76
                $handler
77
            );
78
79
            // Create instance of target class
80
            $controller = new $controllerName();
81
            // Create class method name with prefix
82
            $methodName = $this->actionName.$action;
83
84
            $parameterMap = $this->getMethodParameters(
85
                $controllerName,
86
                $methodName
87
            );
88
89
            $methodParameters = $this->getFunctionArgumentsControlers(
90
                $parameterMap,
91
                $route->getParameters(),
92
                $routePlaceholders,
93
                $handler
94
            );
95
96
            return call_user_func_array(
97
                [
98
                    $controller,
99
                    $methodName
100
                ],
101
                $methodParameters
102
            );
103
        }
104
105
        return $this->dispatchNotFound();
106
    }
107
108
    /**
109
     * Called if any of route did not match the request.
110
     *
111
     * @return mixed
112
     */
113
    public function dispatchNotFound()
114
    {
115
        if (isset($this->notFoundHandler)) {
116
            $notFoundHandler = $this->notFoundHandler;
117
            $controllerName  = implode(
118
                '\\',
119
                [
120
                    $this->baseNamespace,
121
                    $this->controllerName,
122
                    $notFoundHandler['controler']
123
                ]
124
            );
125
126
            $controllerInstance = new $controllerName();
127
128
            return call_user_func(
129
                [
130
                    $controllerInstance,
131
                    $this->actionName.$notFoundHandler['action']
132
                ]
133
            );
134
        }
135
136
        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
    public function setBaseNamespace(string $baseNamespace)
147
    {
148
        $this->baseNamespace = $baseNamespace;
149
150
        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
    public function setNotFoundHandler($handler)
162
    {
163
        $this->notFoundHandler = $handler;
164
165
        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
    private function resolveControlerAction($matches, $parameters, $handler)
180
    {
181
        $controler = $handler['controler'];
182
        $action    = $handler['action'];
183
        $namespace = $this->resolveNamespace($parameters, $handler, $matches);
184
185
        if ($this->isPlaceholder($action)) {
186
            $transformed = $this->transformHandler($matches, $parameters, $handler);
187
            if ($this->isPlaceholder($controler)) {
188
                $controler = $namespace.'\\'.$transformed['controler'];
189
            } else {
190
                $controler = $namespace.'\\'.$controler;
191
            }
192
193
            return [
194
                $controler,
195
                $transformed['action']
196
            ];
197
        }
198
199
        return [
200
            $namespace.'\\'.$controler,
201
            $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
    private function transformHandler($matches, $parameters, $handler)
215
    {
216
        $transformed = [];
217
        foreach ($handler as $target => $placeholder) {
218
            foreach ($parameters as $key => $parameterName) {
219
                if ($parameterName[0][0] == $placeholder) {
220
                    if ($target == 'controler') {
221
                        $transformed[$target] = $matches[$key].'Controler';
222
                    } else {
223
                        $transformed[$target] = $matches[$key];
224
                    }
225
                }
226
            }
227
        }
228
229
        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
    private function resolveNamespace($parameters, $handler, $matches)
242
    {
243
        if (isset($handler['module'])) {
244
            $moduleName = $handler['module'];
245
            if ($this->isPlaceholder($moduleName)) {
246
                foreach ($handler as $target => $placeholder) {
247
                    foreach ($parameters as $key => $parameterName) {
248
                        if ($parameterName[0][0] == $placeholder) {
249
                            if ($target == 'module') {
250
                                $moduleName = $matches[$key];
251
                            }
252
                        }
253
                    }
254
                }
255
            }
256
257
            return implode(
258
                '\\',
259
                [
260
                    $this->baseNamespace,
261
                    $this->moduleName,
262
                    $moduleName,
263
                    $this->controllerName
264
                ]
265
            );
266
        }
267
268
        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
    private function isPlaceholder(string $value) : bool
279
    {
280
        if (strrchr($value, '}') && (0 === strpos($value, '{'))) {
281
            return true;
282
        }
283
284
        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
    private function getFunctionArgumentsControlers(
298
        $paramMap,
299
        $matches,
300
        $parameters,
301
        $handlers
302
    ) {
303
        $output  = [];
304
        $matches = array_values($matches);
305
306
        if (isset($parameters)) {
307
            foreach ($handlers as $placeholder) {
308
                if ($this->isPlaceholder($placeholder)) {
309
                    foreach ($parameters as $key => $parameterName) {
310
                        if ($parameterName[0][0] == $placeholder) {
311
                            unset($parameters[$key]);
312
                            unset($matches[$key]);
313
                        }
314
                    }
315
                }
316
            }
317
318
            $parameters = array_values($parameters);
319
            $matches    = array_values($matches);
320
321 View Code Duplication
            foreach ($parameters as $key => $valueName) {
322
                foreach ($paramMap as $possition => $value) {
323
                    if ($value == $valueName[1][0]) {
324
                        $output[] = $matches[$possition];
325
                    }
326
                }
327
            }
328
        }
329
330
        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
    private function getMethodParameters(string $class, string $methodName) : array
342
    {
343
        $methodReflection = new ReflectionMethod($class, $methodName);
344
        $parametersName   = [];
345
346
        foreach ($methodReflection->getParameters() as $parameter) {
347
            $parametersName[] = $parameter->name;
348
        }
349
350
        return $parametersName;
351
    }
352
}
353