Completed
Push — master ( ee0121...2fa707 )
by Sinnarasa
06:26
created

ArrayMatcher::addDispatcher()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 2
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
        $this->resolver = $resolver;
54
    }
55
56
    /**
57
     * @param string $resolver
58
     */
59
    public function addResolver($resolver){
60
        $this->resolver[] = $resolver;
61
    }
62
63
    /**
64
     * @return array
65
     */
66
    public function getResolver()
67
    {
68
        return $this->resolver;
69
    }
70
71
    /**
72
     * @param array $dispatcher
73
     */
74
    public function setDispatcher($dispatcher = []){
75
        $this->dispatcher = $dispatcher;
76
    }
77
78
    /**
79
     * @param $method
80
     * @param $class
81
     * @return mixed|void
82
     * @internal param array $dispatcher
83
     */
84
    public function addDispatcher($method,$class){
85
        $this->dispatcher[$method] = $class;
86
    }
87
88
    /**
89
     * @return bool
90
     */
91
    public function match()
92
    {
93
        $this->request = [];
94
        for ($i = 0; $i < $this->router->collection->countRoutes; ++$i) {
95
            $this->request['prefix'] = ($this->router->collection->getRoutes('prefix_' . $i) != '') ? $this->router->collection->getRoutes('prefix_' . $i) : '';
96
            $this->request['subdomain'] = ($this->router->collection->getRoutes('subdomain_' . $i) != '') ? $this->router->collection->getRoutes('subdomain_' . $i) : '';
97
            foreach ($this->router->collection->getRoutes('routes_' . $i) as $route => $params) {
98
                $this->request['params'] = $params;
99
                $this->request['collection_index'] = $i;
100
                if($this->checkSubdomain($route)) {
101
                    $route = strstr($route, '/');
102
                    $this->request['route'] = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], '/' . trim(trim($this->request['prefix'], '/') . '/' . trim($route, '/'), '/'));
103
                    if ($this->routeMatch('#^' . $this->request['route'] . '$#')) {
104
                        $this->setCallback();
105
                        return $this->generateTarget();
106
                    }
107
                }
108
            }
109
        }
110
        return false;
111
    }
112
113
    /**
114
     * @param $route
115
     * @return bool
116
     */
117
    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...
118
        $url = (isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . ($host = (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']));
119
        $domain = $this->router->collection->getDomain($url);
120
        if(!empty($this->request['subdomain']) && $route[0] == '/') $route = trim($this->request['subdomain'],'.').'.'.$domain.$route;
121
        if($route[0] == '/'){
122
            return ($host != $domain) ? false : true;
123
        }elseif($route[0] != '/' && $host != $domain){
124
            $route = substr($route, 0, strpos($route, "/"));
125
            $route = str_replace('{host}', $domain, $route);
126
            $route = preg_replace_callback('#{subdomain}#', [$this, 'subdomainMatch'], $route);
127
            if (preg_match('#^' . $route . '$#', $host, $this->request['called_subdomain'])) {
128
                $this->request['called_subdomain'] = array_shift($this->request['called_subdomain']);
129
                return true;
130
            }
131
        }
132
        return false;
133
    }
134
135
    /**
136
     * @return string
137
     */
138
    private function subdomainMatch()
139
    {
140
        if (is_array($this->request['params']) && isset($this->request['params']['subdomain'])) {
141
            $this->request['params']['subdomain'] = str_replace('(', '(?:', $this->request['params']['subdomain']);
142
            return '(' . $this->request['params']['subdomain'] . ')';
143
        }
144
        return '([^/]+)';
145
    }
146
147
    /**
148
     * @param $match
149
     * @return string
150
     */
151
    private function paramMatch($match)
152
    {
153
        if (is_array($this->request['params']) && isset($this->request['params']['arguments'][$match[1]])) {
154
            $this->request['params']['arguments'][$match[1]] = str_replace('(', '(?:', $this->request['params']['arguments'][$match[1]]);
155
            return '(' . $this->request['params']['arguments'][$match[1]] . ')';
156
        }
157
        return '([^/]+)';
158
    }
159
160
    /**
161
     * @param $regex
162
     * @return bool
163
     */
164
    private function routeMatch($regex)
165
    {
166
        if (substr($this->request['route'], -1) == '*') {
167
            $pos = strpos($this->request['route'], '*');
168 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...
169
                return true;
170
        }
171 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...
172
            array_shift($this->request['parameters']);
173
            return true;
174
        }
175
        return false;
176
    }
177
178
    /**
179
     * @return bool
180
     */
181
    private function generateTarget()
182
    {
183
        if($this->validMethod())
184 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...
185
                if (is_array($target = call_user_func_array([$this,$resolver],[$this->router->route->getCallback()]))) {
186
                    $this->setTarget($target);
187
                    $this->router->response->setStatusCode(202);
188
                    return true;
189
                }
190
        $this->router->response->setStatusCode(405);
191
        return false;
192
    }
193
194
    /**
195
     * @param array $target
196
     */
197 View Code Duplication
    public function setTarget($target = []){
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...
198
        $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
199
        $this->router->route->setDetail($this->request);
200
        $this->router->route->setTarget($target);
201
        $this->router->route->addTarget('block', $this->router->collection->getRoutes('block_'.$index));
202
        $this->router->route->addTarget('view_dir', $this->router->collection->getRoutes('view_dir_'.$index));
203
    }
204
205
    /**
206
     *
207
     */
208
    private function setCallback(){
209
        if (isset($this->request['params'])) {
210
            if(is_callable($this->request['params']))
211
                $this->router->route->setCallback($this->request['params']);
212
            else {
213
                (is_array($this->request['params']) && isset($this->request['params']['use']))
214
                    ? $this->router->route->setCallback($this->request['params']['use'])
215
                    : $this->router->route->setCallback($this->request['params']);
216
                if (isset($this->request['params']['name'])) $this->router->route->setName($this->request['params']['name']);
217
                if (isset($this->request['params']['method'])) $this->request['params']['method'] = is_array($this->request['params']['method']) ? $this->request['params']['method'] : [$this->request['params']['method']];
218
            }
219
        }
220
    }
221
222
    /**
223
     * @return bool
224
     */
225
    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...
226
    {
227
        if(is_callable($this->request['params']))return true;
228
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
229
            return (isset($this->request['params']['ajax']) && $this->request['params']['ajax'] === true) ? true : false;
230
        $method = (isset($this->request['params']['method'])) ? $this->request['params']['method'] : ['GET'];
231
        return (in_array($this->router->route->getMethod(), $method)) ? true : false;
232
    }
233
234
    /**
235
     * @param $callback
236
     * @return array|bool
237
     * @throws \Exception
238
     */
239 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...
240
        if(is_array($cls = $this->isClosure($callback))) {
241
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
242
                return array_merge(array_merge($cls, $tpl),[
243
                    'dispatcher' => $this->dispatcher['isClosureAndTemplate']
244
                ]);
245
            }
246
            return $cls;
247
        }
248
        return false;
249
    }
250
251
    /**
252
     * @param $callback
253
     * @return array|bool
254
     * @throws \Exception
255
     */
256 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...
257
        if(is_array($ctrl = $this->isController($callback))) {
258
            if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) {
259
                return array_merge(array_merge($ctrl, $tpl),[
260
                    'dispatcher' => $this->dispatcher['isControllerAndTemplate']
261
                ]);
262
            }
263
            return $ctrl;
264
        }
265
        return false;
266
    }
267
268
269
    /**
270
     * @param $callback
271
     * @return bool|array
272
     */
273
    public function isClosure($callback)
274
    {
275
        if (is_callable($callback)) {
276
            return [
277
                'dispatcher' => $this->dispatcher['isClosure'],
278
                'closure' => $callback
279
            ];
280
        }
281
        return false;
282
    }
283
284
    /**
285
     * @param $callback
286
     * @throws \Exception
287
     * @return bool|array
288
     */
289
    public function isController($callback)
290
    {
291
        if (is_string($callback) && strpos($callback, '@') !== false) {
292
            $routes = explode('@', $callback);
293
            if (!isset($routes[1])) $routes[1] = 'index';
294
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
295
            $class = (class_exists($routes[0]))
296
                ? $routes[0]
297
                : $this->router->collection->getRoutes()['ctrl_namespace_'.$index].$routes[0];
298
            if (!class_exists($class))
299
                throw new \Exception('Class "' . $class . '." is not found');
300
            if (method_exists($class, $routes[1])) {
301
                return [
302
                    'dispatcher' => $this->dispatcher['isController'],
303
                    'di' => $this->router->getConfig()['di'],
304
                    'controller' => $class,
305
                    'action' => $routes[1]
306
                ];
307
            }
308
            throw new \Exception('The required method "' . $routes[1] . '" is not found in "' . $class . '"');
309
        }
310
        return false;
311
    }
312
313
    /**
314
     * @param $callback
315
     * @throws \Exception
316
     * @return bool|array
317
     */
318
    public function isTemplate($callback)
319
    {
320
        if(is_string($callback)) {
321
            $path = trim($callback, '/');
322
            $extension = substr(strrchr($path, "."), 1);
323
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
324
            $viewDir = $this->router->collection->getRoutes('view_dir_' . $index);
325
            $target = null;
326
            if (in_array('.' . $extension, $this->router->getConfig()['templateExtension']) && (is_file($fullPath = $viewDir . $path) || is_file($fullPath = $path)))
327
                $target = $fullPath;
328
            else {
329
                foreach ($this->router->getConfig()['templateExtension'] as $ext) {
330
                    if (is_file($fullPath = $viewDir . $path . $ext) || is_file($fullPath = $path . $ext)) {
331
                        $target = $fullPath;
332
                        $extension = substr(strrchr($ext, "."), 1);
333
                        break;
334
                    }
335
                }
336
            }
337
            if(is_null($target))
338
                throw new \Exception('Template file "' . $path . '" is not found in "' . $viewDir . '"');
339
            return [
340
                'dispatcher' => $this->dispatcher['isTemplate'],
341
                'template'   => $target,
342
                'extension'  => $extension,
343
                'callback'   => $this->router->getConfig()['templateCallback']
344
            ];
345
        }
346
        return false;
347
    }
348
349
}
350