Passed
Push — master ( bdb393...1fedf8 )
by Sinnarasa
01:46
created

ArrayMatcher::checkRequest()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 2
nop 1
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
        $regex = (substr($this->request['route'], -1) == '*') ? '#^' . $this->request['route'] . '#' : $regex;
175
        if (preg_match($regex, $this->router->route->getUrl(), $this->request['parameters'])) {
176
            array_shift($this->request['parameters']);
177
            return true;
178
        }
179
        return false;
180
    }
181
182
    /**
183
     * @return bool
184
     */
185
    private function generateTarget()
186
    {
187
        if ($this->validMethod()) {
188
            foreach ($this->resolver as $resolver) {
189
                if (is_array($target = call_user_func_array([$this, $resolver], [$this->router->route->getCallback()]))) {
190
                    $this->setTarget($target);
191
                    return true;
192
                }
193
            }
194
        }
195
        $this->router->response->setStatusCode(405);
196
        return false;
197
    }
198
199
    /**
200
     * @param array $target
201
     */
202
    public function setTarget($target = [])
203
    {
204
        $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
205
        $this->checkRequest('subdomain');
206
        $this->checkRequest('prefix');
207
        $this->router->route->setDetail($this->request);
208
        $this->router->route->setTarget($target);
209
        $this->router->route->addTarget('block', $this->router->collection->getRoutes('block_' . $index));
210
        $this->router->route->addTarget('view_dir', $this->router->collection->getRoutes('view_dir_' . $index));
211
        $this->router->route->addTarget('params', $this->router->collection->getRoutes('params_' . $index));
212
    }
213
214
    /**
215
     * @param $key
216
     */
217
    private function checkRequest($key)
218
    {
219
        if (strpos($this->request[$key], ':') !== false && isset($this->request['parameters'][0])) {
220
            $replacements = $this->request['parameters'];
221
            $keys = [];
222
            $this->request['@' . $key] = $this->request[$key];
223
            $this->request[$key] = preg_replace_callback('#:([\w]+)#', function ($matches) use (&$replacements, &$keys) {
224
                $keys[$matches[0]] = $replacements[0];
225
                return array_shift($replacements);
226
            }, $this->request[$key]);
227
            $this->request['keys'] = $keys;
228
            $this->request['parameters'] = $replacements;
229
        }
230
    }
231
232
    /**
233
     *
234
     */
235
    private function setCallback()
236
    {
237
        if (isset($this->request['params'])) {
238
            if (is_callable($this->request['params'])) {
239
                $this->router->route->setCallback($this->request['params']);
240
            } else {
241
                if (is_array($this->request['params']) && isset($this->request['params']['use'])) {
242
                    if (is_array($this->request['params']['use']) && isset($this->request['params']['use'][$this->router->route->getMethod()])) {
243
                        $this->router->route->setCallback($this->request['params']['use'][$this->router->route->getMethod()]);
244
                    } elseif (!is_array($this->request['params']['use'])) {
245
                        $this->router->route->setCallback($this->request['params']['use']);
246
                    }
247
                } else {
248
                    $this->router->route->setCallback($this->request['params']);
249
                }
250
                if (isset($this->request['params']['name'])) {
251
                    $this->router->route->setName($this->request['params']['name']);
252
                }
253
                if (isset($this->request['params']['method'])) {
254
                    $this->request['params']['method'] = is_array($this->request['params']['method']) ? $this->request['params']['method'] : [$this->request['params']['method']];
255
                }
256
            }
257
        }
258
    }
259
260
    /**
261
     * @return bool
262
     */
263
    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...
264
    {
265
        if (is_callable($this->request['params'])) {
266
            return true;
267
        }
268
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
269
            return (isset($this->request['params']['ajax']) && $this->request['params']['ajax'] === true) ? true : false;
270
        }
271
        $method = (isset($this->request['params']['method'])) ? $this->request['params']['method'] : ['GET'];
272
        return (in_array($this->router->route->getMethod(), $method)) ? true : false;
273
    }
274
275
    /**
276
     * @param $callback
277
     * @return array|bool
278
     * @throws \Exception
279
     */
280 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...
281
    {
282
        if (is_array($cls = $this->isClosure($callback))) {
283
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
284
                return array_merge(array_merge($cls, $tpl), [
285
                    'dispatcher' => $this->dispatcher['isClosureAndTemplate']
286
                ]);
287
            }
288
            return $cls;
289
        }
290
        return false;
291
    }
292
293
    /**
294
     * @param $callback
295
     * @return array|bool
296
     * @throws \Exception
297
     */
298 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...
299
    {
300
        if (is_array($ctrl = $this->isController($callback))) {
301
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
302
                return array_merge(array_merge($ctrl, $tpl), [
303
                    'dispatcher' => $this->dispatcher['isControllerAndTemplate']
304
                ]);
305
            }
306
            return $ctrl;
307
        }
308
        return false;
309
    }
310
311
312
    /**
313
     * @param $callback
314
     * @return bool|array
315
     */
316
    public function isClosure($callback)
317
    {
318
        if (is_callable($callback)) {
319
            return [
320
                'dispatcher' => $this->dispatcher['isClosure'],
321
                'closure' => $callback
322
            ];
323
        }
324
        return false;
325
    }
326
327
    /**
328
     * @param $callback
329
     * @throws \Exception
330
     * @return bool|array
331
     */
332
    public function isController($callback)
333
    {
334
        if (is_string($callback) && strpos($callback, '@') !== false) {
335
            $routes = explode('@', $callback);
336
            if (!isset($routes[1])) $routes[1] = 'index';
337
            if ($routes[1] == '{method}') {
338
                $params = explode('/', preg_replace('#' . str_replace('*', '', $this->request['route']) . '#', '', $this->router->route->getUrl()));
339
                $routes[1] = $params[0];
340
                array_shift($params);
341
                array_merge($this->request['parameters'], $params);
342
                if (preg_match('/[A-Z]/', $routes[1])) return false;
343
                $routes[1] = lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $routes[1]))));
344
            }
345
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
346
            $class = (class_exists($routes[0]))
347
                ? $routes[0]
348
                : $this->router->collection->getRoutes()['ctrl_namespace_' . $index] . $routes[0];
349
            if (!class_exists($class)) {
350
                throw new \Exception('Class "' . $class . '." is not found');
351
            }
352
            if (method_exists($class, $routes[1])) {
353
                return [
354
                    'dispatcher' => $this->dispatcher['isController'],
355
                    'di' => $this->router->getConfig()['di'],
356
                    'controller' => $class,
357
                    'action' => $routes[1]
358
                ];
359
            }
360
            if (!strpos($callback, '{method}') !== false) {
361
                throw new \Exception('The required method "' . $routes[1] . '" is not found in "' . $class . '"');
362
            }
363
        }
364
        return false;
365
    }
366
367
    /**
368
     * @param $callback
369
     * @throws \Exception
370
     * @return bool|array
371
     */
372
    public function isTemplate($callback)
373
    {
374
        if (is_string($callback) && strpos($callback, '@') === false) {
375
            $path = trim($callback, '/');
376
            $extension = substr(strrchr($path, "."), 1);
377
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
378
            $viewDir = $this->router->collection->getRoutes('view_dir_' . $index);
379
            $target = null;
380
            if (in_array('.' . $extension, $this->router->getConfig()['templateExtension']) && (is_file($fullPath = $viewDir . $path) || is_file($fullPath = $path))) {
381
                $target = $fullPath;
382
            } else {
383
                foreach ($this->router->getConfig()['templateExtension'] as $ext) {
384
                    if (is_file($fullPath = $viewDir . $path . $ext) || is_file($fullPath = $path . $ext)) {
385
                        $target = $fullPath;
386
                        $extension = substr(strrchr($ext, "."), 1);
387
                        break;
388
                    }
389
                }
390
            }
391
            if (is_null($target)) {
392
                throw new \Exception('Template file "' . $path . '" is not found in "' . $viewDir . '"');
393
            }
394
            return [
395
                'dispatcher' => $this->dispatcher['isTemplate'],
396
                'template' => $target,
397
                'extension' => $extension,
398
                'callback' => $this->router->getConfig()['templateCallback']
399
            ];
400
        }
401
        return false;
402
    }
403
404
}
405