Test Failed
Push — master ( 8cc3ab...66da72 )
by Sinnarasa
03:07
created

ArrayMatcher::checkRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace JetFire\Routing\Matcher;
4
5
6
use JetFire\Routing\Router;
7
8
/**
9
 * Class RoutesMatch
10
 * @package JetFire\Routing\Match
11
 */
12
class ArrayMatcher implements MatcherInterface
13
{
14
15
    /**
16
     * @var
17
     */
18
    private $router;
19
20
    /**
21
     * @var array
22
     */
23
    private $request = [];
24
25
    /**
26
     * @var array
27
     */
28
    private $resolver = ['isClosureAndTemplate', 'isControllerAndTemplate', 'isTemplate'];
29
30
    /**
31
     * @var array
32
     */
33
    private $dispatcher = [
34
        'isClosure' => 'JetFire\Routing\Dispatcher\ClosureDispatcher',
35
        'isController' => 'JetFire\Routing\Dispatcher\ControllerDispatcher',
36
        'isTemplate' => 'JetFire\Routing\Dispatcher\TemplateDispatcher',
37
        'isControllerAndTemplate' => ['JetFire\Routing\Dispatcher\ControllerDispatcher', 'JetFire\Routing\Dispatcher\TemplateDispatcher'],
38
        'isClosureAndTemplate' => ['JetFire\Routing\Dispatcher\ClosureDispatcher', 'JetFire\Routing\Dispatcher\TemplateDispatcher'],
39
    ];
40
41
    /**
42
     * @param Router $router
43
     */
44
    public function __construct(Router $router)
45
    {
46
        $this->router = $router;
47
    }
48
49
    /**
50
     * @param array $resolver
51
     */
52
    public function setResolver($resolver = [])
53
    {
54
        $this->resolver = $resolver;
55
    }
56
57
    /**
58
     * @param string $resolver
59
     */
60
    public function addResolver($resolver)
61
    {
62
        $this->resolver[] = $resolver;
63
    }
64
65
    /**
66
     * @return array
67
     */
68
    public function getResolver()
69
    {
70
        return $this->resolver;
71
    }
72
73
    /**
74
     * @param array $dispatcher
75
     */
76
    public function setDispatcher($dispatcher = [])
77
    {
78
        $this->dispatcher = $dispatcher;
79
    }
80
81
    /**
82
     * @param $method
83
     * @param $class
84
     * @internal param array $dispatcher
85
     */
86
    public function addDispatcher($method, $class)
87
    {
88
        $this->dispatcher[$method] = $class;
89
    }
90
91
    /**
92
     * @return bool
93
     */
94
    public function match()
95
    {
96
        $this->request = [];
97
        for ($i = 0; $i < $this->router->collection->countRoutes; ++$i) {
98
            $this->request['prefix'] = ($this->router->collection->getRoutes('prefix_' . $i) != '') ? $this->router->collection->getRoutes('prefix_' . $i) : '';
99
            $this->request['subdomain'] = ($this->router->collection->getRoutes('subdomain_' . $i) != '') ? $this->router->collection->getRoutes('subdomain_' . $i) : '';
100
            foreach ($this->router->collection->getRoutes('routes_' . $i) as $route => $params) {
101
                $this->request['params'] = $params;
102
                $this->request['collection_index'] = $i;
103
                if ($this->checkSubdomain($route)) {
104
                    $route = strstr($route, '/');
105
                    $this->request['route'] = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], '/' . trim(trim($this->request['prefix'], '/') . '/' . trim($route, '/'), '/'));
106
                    if ($this->routeMatch('#^' . $this->request['route'] . '$#')) {
107
                        $this->setCallback();
108
                        if (!is_null($response = $this->generateTarget())) return $response;
109
                    }
110
                }
111
            }
112
        }
113
        return false;
114
    }
115
116
    /**
117
     * @param $route
118
     * @return bool
119
     */
120
    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...
121
    {
122
        $url = (isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . ($host = (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST']));
123
        $host = explode(':', $host)[0];
124
        $domain = $this->router->collection->getDomain($url);
125
        if (!empty($this->request['subdomain']) && $route[0] == '/') $route = trim($this->request['subdomain'], '.') . '.' . $domain . $route;
126
        if ($route[0] == '/') {
127
            return ($host != $domain) ? false : true;
128
        } elseif ($route[0] != '/' && $host != $domain) {
129
            $route = substr($route, 0, strpos($route, "/"));
130
            $route = str_replace('{host}', $domain, $route);
131
            $route = preg_replace_callback('#{subdomain}#', [$this, 'subdomainMatch'], $route);
132
            if (preg_match('#^' . $route . '$#', $host, $this->request['called_subdomain'])) {
133
                $this->request['called_subdomain'] = array_shift($this->request['called_subdomain']);
134
                $this->request['subdomain'] = str_replace('.' . $domain, '', $host);
135
                return true;
136
            }
137
        }
138
        return false;
139
    }
140
141
    /**
142
     * @return string
143
     */
144
    private function subdomainMatch()
145
    {
146
        if (is_array($this->request['params']) && isset($this->request['params']['subdomain'])) {
147
            return '(' . $this->request['params']['subdomain'] . ')';
148
        }
149
        return '([^/]+)';
150
    }
151
152
    /**
153
     * @param $match
154
     * @return string
155
     */
156
    private function paramMatch($match)
157
    {
158
        if (is_array($this->request['params']) && isset($this->request['params']['arguments'][$match[1]])) {
159
            $this->request['params']['arguments'][$match[1]] = str_replace('(', '(?:', $this->request['params']['arguments'][$match[1]]);
160
            return '(' . $this->request['params']['arguments'][$match[1]] . ')';
161
        }
162
        return '([^/]+)';
163
    }
164
165
    /**
166
     * @param $regex
167
     * @return bool
168
     */
169
    private function routeMatch($regex)
170
    {
171
        if (substr($this->request['route'], -1) == '*') {
172
            $pos = strpos($this->request['route'], '*');
173 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...
174
                return true;
175
        }
176 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...
177
            array_shift($this->request['parameters']);
178
            return true;
179
        }
180
        return false;
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    private function generateTarget()
187
    {
188
        if ($this->validMethod()) {
189 View Code Duplication
            foreach ($this->resolver as $resolver) {
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...
190
                if (is_array($target = call_user_func_array([$this, $resolver], [$this->router->route->getCallback()]))) {
191
                    $this->setTarget($target);
192
                    if ($this->router->middleware->globalMiddleware() === false || $this->router->middleware->blockMiddleware() === false || $this->router->middleware->classMiddleware() === false || $this->router->middleware->routeMiddleware() === false)
193
                        return null;
194
                    $this->router->response->setStatusCode(202);
195
                    return true;
196
                }
197
            }
198
            return false;
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) {
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
                (is_array($this->request['params']) && isset($this->request['params']['use']))
240
                    ? $this->router->route->setCallback($this->request['params']['use'])
241
                    : $this->router->route->setCallback($this->request['params']);
242
                if (isset($this->request['params']['name'])) $this->router->route->setName($this->request['params']['name']);
243
                if (isset($this->request['params']['method'])) $this->request['params']['method'] = is_array($this->request['params']['method']) ? $this->request['params']['method'] : [$this->request['params']['method']];
244
            }
245
        }
246
    }
247
248
    /**
249
     * @return bool
250
     */
251
    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...
252
    {
253
        if (is_callable($this->request['params'])) return true;
254
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
255
            return (isset($this->request['params']['ajax']) && $this->request['params']['ajax'] === true) ? true : false;
256
        $method = (isset($this->request['params']['method'])) ? $this->request['params']['method'] : ['GET'];
257
        return (in_array($this->router->route->getMethod(), $method)) ? true : false;
258
    }
259
260
    /**
261
     * @param $callback
262
     * @return array|bool
263
     * @throws \Exception
264
     */
265 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...
266
    {
267
        if (is_array($cls = $this->isClosure($callback))) {
268
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
269
                return array_merge(array_merge($cls, $tpl), [
270
                    'dispatcher' => $this->dispatcher['isClosureAndTemplate']
271
                ]);
272
            }
273
            return $cls;
274
        }
275
        return false;
276
    }
277
278
    /**
279
     * @param $callback
280
     * @return array|bool
281
     * @throws \Exception
282
     */
283 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...
284
    {
285
        if (is_array($ctrl = $this->isController($callback))) {
286
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
287
                return array_merge(array_merge($ctrl, $tpl), [
288
                    'dispatcher' => $this->dispatcher['isControllerAndTemplate']
289
                ]);
290
            }
291
            return $ctrl;
292
        }
293
        return false;
294
    }
295
296
297
    /**
298
     * @param $callback
299
     * @return bool|array
300
     */
301
    public function isClosure($callback)
302
    {
303
        if (is_callable($callback)) {
304
            return [
305
                'dispatcher' => $this->dispatcher['isClosure'],
306
                'closure' => $callback
307
            ];
308
        }
309
        return false;
310
    }
311
312
    /**
313
     * @param $callback
314
     * @throws \Exception
315
     * @return bool|array
316
     */
317
    public function isController($callback)
318
    {
319
        if (is_string($callback) && strpos($callback, '@') !== false) {
320
            $routes = explode('@', $callback);
321
            if (!isset($routes[1])) $routes[1] = 'index';
322
            if ($routes[1] == '{method}') {
323
                $this->request['parameters'] = explode('/', str_replace(str_replace('*', '', $this->request['route']), '', $this->router->route->getUrl()));
324
                $routes[1] = $this->request['parameters'][0];
325
                array_shift($this->request['parameters']);
326
                if (preg_match('/[A-Z]/', $routes[1])) return false;
327
                $routes[1] = lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $routes[1]))));
328
            }
329
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
330
            $class = (class_exists($routes[0]))
331
                ? $routes[0]
332
                : $this->router->collection->getRoutes()['ctrl_namespace_' . $index] . $routes[0];
333
            if (!class_exists($class))
334
                throw new \Exception('Class "' . $class . '." is not found');
335
            if (method_exists($class, $routes[1])) {
336
                return [
337
                    'dispatcher' => $this->dispatcher['isController'],
338
                    'di' => $this->router->getConfig()['di'],
339
                    'controller' => $class,
340
                    'action' => $routes[1]
341
                ];
342
            }
343
            if (!strpos($callback, '{method}') !== false)
344
                throw new \Exception('The required method "' . $routes[1] . '" is not found in "' . $class . '"');
345
        }
346
        return false;
347
    }
348
349
    /**
350
     * @param $callback
351
     * @throws \Exception
352
     * @return bool|array
353
     */
354
    public function isTemplate($callback)
355
    {
356
        if (is_string($callback) && strpos($callback, '@') === false) {
357
            $path = trim($callback, '/');
358
            $extension = substr(strrchr($path, "."), 1);
359
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
360
            $viewDir = $this->router->collection->getRoutes('view_dir_' . $index);
361
            $target = null;
362
            if (in_array('.' . $extension, $this->router->getConfig()['templateExtension']) && (is_file($fullPath = $viewDir . $path) || is_file($fullPath = $path)))
363
                $target = $fullPath;
364
            else {
365
                foreach ($this->router->getConfig()['templateExtension'] as $ext) {
366
                    if (is_file($fullPath = $viewDir . $path . $ext) || is_file($fullPath = $path . $ext)) {
367
                        $target = $fullPath;
368
                        $extension = substr(strrchr($ext, "."), 1);
369
                        break;
370
                    }
371
                }
372
            }
373
            if (is_null($target))
374
                throw new \Exception('Template file "' . $path . '" is not found in "' . $viewDir . '"');
375
            return [
376
                'dispatcher' => $this->dispatcher['isTemplate'],
377
                'template' => $target,
378
                'extension' => $extension,
379
                'callback' => $this->router->getConfig()['templateCallback']
380
            ];
381
        }
382
        return false;
383
    }
384
385
}
386