Test Failed
Push — master ( 4c92de...3b4c18 )
by Sinnarasa
04:16
created

ArrayMatcher::setCallback()   C

Complexity

Conditions 11
Paths 26

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 5.3305
c 0
b 0
f 0
cc 11
eloc 16
nc 26
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace JetFire\Routing\Matcher;
4
5
6
use JetFire\Routing\Dispatcher\ClosureDispatcher;
7
use JetFire\Routing\Dispatcher\ControllerDispatcher;
8
use JetFire\Routing\Dispatcher\TemplateDispatcher;
9
use JetFire\Routing\Router;
10
11
/**
12
 * Class RoutesMatch
13
 * @package JetFire\Routing\Match
14
 */
15
class ArrayMatcher implements MatcherInterface
16
{
17
18
    /**
19
     * @var
20
     */
21
    private $router;
22
23
    /**
24
     * @var array
25
     */
26
    private $request = [];
27
28
    /**
29
     * @var array
30
     */
31
    private $resolver = ['isClosureAndTemplate', 'isControllerAndTemplate', 'isTemplate'];
32
33
    /**
34
     * @var array
35
     */
36
    private $dispatcher = [
37
        'isClosure' => ClosureDispatcher::class,
38
        'isController' => ControllerDispatcher::class,
39
        'isTemplate' => TemplateDispatcher::class,
40
        'isControllerAndTemplate' => [ControllerDispatcher::class, TemplateDispatcher::class],
41
        'isClosureAndTemplate' => [ClosureDispatcher::class, TemplateDispatcher::class],
42
    ];
43
44
    /**
45
     * @param Router $router
46
     */
47
    public function __construct(Router $router)
48
    {
49
        $this->router = $router;
50
    }
51
52
    /**
53
     * @param array $resolver
54
     */
55
    public function setResolver($resolver = [])
56
    {
57
        $this->resolver = $resolver;
58
    }
59
60
    /**
61
     * @param string $resolver
62
     */
63
    public function addResolver($resolver)
64
    {
65
        $this->resolver[] = $resolver;
66
    }
67
68
    /**
69
     * @return array
70
     */
71
    public function getResolver()
72
    {
73
        return $this->resolver;
74
    }
75
76
    /**
77
     * @param array $dispatcher
78
     */
79
    public function setDispatcher($dispatcher = [])
80
    {
81
        $this->dispatcher = $dispatcher;
82
    }
83
84
    /**
85
     * @param $method
86
     * @param $class
87
     * @internal param array $dispatcher
88
     */
89
    public function addDispatcher($method, $class)
90
    {
91
        $this->dispatcher[$method] = $class;
92
    }
93
94
    /**
95
     * @return bool
96
     */
97
    public function match()
98
    {
99
        $this->request = [];
100
        for ($i = 0; $i < $this->router->collection->countRoutes; ++$i) {
101
            $this->request['prefix'] = ($this->router->collection->getRoutes('prefix_' . $i) != '') ? $this->router->collection->getRoutes('prefix_' . $i) : '';
102
            $this->request['subdomain'] = ($this->router->collection->getRoutes('subdomain_' . $i) != '') ? $this->router->collection->getRoutes('subdomain_' . $i) : '';
103
            foreach ($this->router->collection->getRoutes('routes_' . $i) as $route => $params) {
104
                $this->request['params'] = $params;
105
                $this->request['collection_index'] = $i;
106
                if ($this->checkSubdomain($route)) {
107
                    $route = strstr($route, '/');
108
                    $this->request['route'] = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], '/' . trim(trim($this->request['prefix'], '/') . '/' . trim($route, '/'), '/'));
109
                    if ($this->routeMatch('#^' . $this->request['route'] . '$#')) {
110
                        $this->setCallback();
111
                        return $this->generateTarget();
112
                    }
113
                }
114
            }
115
        }
116
        return false;
117
    }
118
119
    /**
120
     * @param $route
121
     * @return bool
122
     */
123
    private function checkSubdomain($route)
0 ignored issues
show
Coding Style introduced by
checkSubdomain uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
124
    {
125
        $url = (isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . ($host = (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST']));
126
        $host = explode(':', $host)[0];
127
        $domain = $this->router->collection->getDomain($url);
128
        if (!empty($this->request['subdomain']) && $route[0] == '/') $route = trim($this->request['subdomain'], '.') . '.' . $domain . $route;
129
        if ($route[0] == '/') {
130
            return ($host != $domain) ? false : true;
131
        } elseif ($route[0] != '/' && $host != $domain) {
132
            $route = substr($route, 0, strpos($route, "/"));
133
            $route = str_replace('{host}', $domain, $route);
134
            $route = preg_replace_callback('#{subdomain}#', [$this, 'subdomainMatch'], $route);
135
            if (preg_match('#^' . $route . '$#', $host, $this->request['called_subdomain'])) {
136
                $this->request['called_subdomain'] = array_shift($this->request['called_subdomain']);
137
                $this->request['subdomain'] = str_replace('.' . $domain, '', $host);
138
                return true;
139
            }
140
        }
141
        return false;
142
    }
143
144
    /**
145
     * @return string
146
     */
147
    private function subdomainMatch()
148
    {
149
        if (is_array($this->request['params']) && isset($this->request['params']['subdomain'])) {
150
            return '(' . $this->request['params']['subdomain'] . ')';
151
        }
152
        return '([^/]+)';
153
    }
154
155
    /**
156
     * @param $match
157
     * @return string
158
     */
159
    private function paramMatch($match)
160
    {
161
        if (is_array($this->request['params']) && isset($this->request['params']['arguments'][$match[1]])) {
162
            $this->request['params']['arguments'][$match[1]] = str_replace('(', '(?:', $this->request['params']['arguments'][$match[1]]);
163
            return '(' . $this->request['params']['arguments'][$match[1]] . ')';
164
        }
165
        return '([^/]+)';
166
    }
167
168
    /**
169
     * @param $regex
170
     * @return bool
171
     */
172
    private function routeMatch($regex)
173
    {
174
        if (substr($this->request['route'], -1) == '*') {
175
            $pos = strpos($this->request['route'], '*');
176 View Code Duplication
            if (substr($this->router->route->getUrl(), 0, $pos) == substr($this->request['route'], 0, $pos) && isset($this->request['params'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
                return true;
178
            }
179
        }
180 View Code Duplication
        if (preg_match($regex, $this->router->route->getUrl(), $this->request['parameters'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
            array_shift($this->request['parameters']);
182
            return true;
183
        }
184
        return false;
185
    }
186
187
    /**
188
     * @return bool
189
     */
190
    private function generateTarget()
191
    {
192
        if ($this->validMethod()) {
193
            foreach ($this->resolver as $resolver) {
194
                if (is_array($target = call_user_func_array([$this, $resolver], [$this->router->route->getCallback()]))) {
195
                    $this->setTarget($target);
196
                    return true;
197
                }
198
            }
199
        }
200
        $this->router->response->setStatusCode(405);
201
        return false;
202
    }
203
204
    /**
205
     * @param array $target
206
     */
207
    public function setTarget($target = [])
208
    {
209
        $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
210
        $this->checkRequest('subdomain');
211
        $this->checkRequest('prefix');
212
        $this->router->route->setDetail($this->request);
213
        $this->router->route->setTarget($target);
214
        $this->router->route->addTarget('block', $this->router->collection->getRoutes('block_' . $index));
215
        $this->router->route->addTarget('view_dir', $this->router->collection->getRoutes('view_dir_' . $index));
216
    }
217
218
    /**
219
     * @param $key
220
     */
221
    private function checkRequest($key)
222
    {
223
        if (strpos($this->request[$key], ':') !== false && isset($this->request['parameters'][0])) {
224
            $this->request['@' . $key] = $this->request[$key];
225
            $this->request[$key] = $this->request['parameters'][0];
226
            unset($this->request['parameters'][0]);
227
        }
228
    }
229
230
    /**
231
     *
232
     */
233
    private function setCallback()
234
    {
235
        if (isset($this->request['params'])) {
236
            if (is_callable($this->request['params'])) {
237
                $this->router->route->setCallback($this->request['params']);
238
            } else {
239
                if (is_array($this->request['params']) && isset($this->request['params']['use'])) {
240
                    if(is_array($this->request['params']['use']) && isset($this->request['params']['use'][$this->router->route->getMethod()])){
241
                        $this->router->route->setCallback($this->request['params']['use'][$this->router->route->getMethod()]);
242
                    }elseif(!is_array($this->request['params']['use'])){
243
                        $this->router->route->setCallback($this->request['params']['use']);
244
                    }
245
                } else {
246
                    $this->router->route->setCallback($this->request['params']);
247
                }
248
                if (isset($this->request['params']['name'])) {
249
                    $this->router->route->setName($this->request['params']['name']);
250
                }
251
                if (isset($this->request['params']['method'])) {
252
                    $this->request['params']['method'] = is_array($this->request['params']['method']) ? $this->request['params']['method'] : [$this->request['params']['method']];
253
                }
254
            }
255
        }
256
    }
257
258
    /**
259
     * @return bool
260
     */
261
    public function validMethod()
0 ignored issues
show
Coding Style introduced by
validMethod uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
262
    {
263
        if (is_callable($this->request['params'])) {
264
            return true;
265
        }
266
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
267
            return (isset($this->request['params']['ajax']) && $this->request['params']['ajax'] === true) ? true : false;
268
        }
269
        $method = (isset($this->request['params']['method'])) ? $this->request['params']['method'] : ['GET'];
270
        return (in_array($this->router->route->getMethod(), $method)) ? true : false;
271
    }
272
273
    /**
274
     * @param $callback
275
     * @return array|bool
276
     * @throws \Exception
277
     */
278 View Code Duplication
    public function isClosureAndTemplate($callback)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
    {
280
        if (is_array($cls = $this->isClosure($callback))) {
281
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
282
                return array_merge(array_merge($cls, $tpl), [
283
                    'dispatcher' => $this->dispatcher['isClosureAndTemplate']
284
                ]);
285
            }
286
            return $cls;
287
        }
288
        return false;
289
    }
290
291
    /**
292
     * @param $callback
293
     * @return array|bool
294
     * @throws \Exception
295
     */
296 View Code Duplication
    public function isControllerAndTemplate($callback)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
297
    {
298
        if (is_array($ctrl = $this->isController($callback))) {
299
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
300
                return array_merge(array_merge($ctrl, $tpl), [
301
                    'dispatcher' => $this->dispatcher['isControllerAndTemplate']
302
                ]);
303
            }
304
            return $ctrl;
305
        }
306
        return false;
307
    }
308
309
310
    /**
311
     * @param $callback
312
     * @return bool|array
313
     */
314
    public function isClosure($callback)
315
    {
316
        if (is_callable($callback)) {
317
            return [
318
                'dispatcher' => $this->dispatcher['isClosure'],
319
                'closure' => $callback
320
            ];
321
        }
322
        return false;
323
    }
324
325
    /**
326
     * @param $callback
327
     * @throws \Exception
328
     * @return bool|array
329
     */
330
    public function isController($callback)
331
    {
332
        if (is_string($callback) && strpos($callback, '@') !== false) {
333
            $routes = explode('@', $callback);
334
            if (!isset($routes[1])) $routes[1] = 'index';
335
            if ($routes[1] == '{method}') {
336
                $this->request['parameters'] = explode('/', str_replace(str_replace('*', '', $this->request['route']), '', $this->router->route->getUrl()));
337
                $routes[1] = $this->request['parameters'][0];
338
                array_shift($this->request['parameters']);
339
                if (preg_match('/[A-Z]/', $routes[1])) return false;
340
                $routes[1] = lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $routes[1]))));
341
            }
342
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
343
            $class = (class_exists($routes[0]))
344
                ? $routes[0]
345
                : $this->router->collection->getRoutes()['ctrl_namespace_' . $index] . $routes[0];
346
            if (!class_exists($class)) {
347
                throw new \Exception('Class "' . $class . '." is not found');
348
            }
349
            if (method_exists($class, $routes[1])) {
350
                return [
351
                    'dispatcher' => $this->dispatcher['isController'],
352
                    'di' => $this->router->getConfig()['di'],
353
                    'controller' => $class,
354
                    'action' => $routes[1]
355
                ];
356
            }
357
            if (!strpos($callback, '{method}') !== false) {
358
                throw new \Exception('The required method "' . $routes[1] . '" is not found in "' . $class . '"');
359
            }
360
        }
361
        return false;
362
    }
363
364
    /**
365
     * @param $callback
366
     * @throws \Exception
367
     * @return bool|array
368
     */
369
    public function isTemplate($callback)
370
    {
371
        if (is_string($callback) && strpos($callback, '@') === false) {
372
            $path = trim($callback, '/');
373
            $extension = substr(strrchr($path, "."), 1);
374
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
375
            $viewDir = $this->router->collection->getRoutes('view_dir_' . $index);
376
            $target = null;
377
            if (in_array('.' . $extension, $this->router->getConfig()['templateExtension']) && (is_file($fullPath = $viewDir . $path) || is_file($fullPath = $path))) {
378
                $target = $fullPath;
379
            } else {
380
                foreach ($this->router->getConfig()['templateExtension'] as $ext) {
381
                    if (is_file($fullPath = $viewDir . $path . $ext) || is_file($fullPath = $path . $ext)) {
382
                        $target = $fullPath;
383
                        $extension = substr(strrchr($ext, "."), 1);
384
                        break;
385
                    }
386
                }
387
            }
388
            if (is_null($target)) {
389
                throw new \Exception('Template file "' . $path . '" is not found in "' . $viewDir . '"');
390
            }
391
            return [
392
                'dispatcher' => $this->dispatcher['isTemplate'],
393
                'template' => $target,
394
                'extension' => $extension,
395
                'callback' => $this->router->getConfig()['templateCallback']
396
            ];
397
        }
398
        return false;
399
    }
400
401
}
402