Completed
Push — master ( 46d40e...8f6f9d )
by Sinnarasa
02:37
created

ArrayMatcher   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 246
Duplicated Lines 2.44 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 56
c 3
b 0
f 0
lcom 1
cbo 4
dl 6
loc 246
rs 6.5957

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A addMatcher() 0 3 1
A getMatcher() 0 4 1
A setDispatcher() 0 3 1
A addDispatcher() 0 3 1
B match() 0 18 5
A paramMatch() 0 8 3
B routeMatch() 6 13 5
B generateTarget() 0 12 5
B setRoute() 0 14 8
B validMethod() 0 8 8
A matchClosure() 0 11 2
C matchController() 0 24 7
C matchTemplate() 0 31 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ArrayMatcher often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ArrayMatcher, and based on these observations, apply Extract Interface, too.

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
     * @var array
21
     */
22
    private $request = [];
23
24
    /**
25
     * @var array
26
     */
27
    private $matcher = ['matchClosure','matchController','matchTemplate'];
28
29
    /**
30
     * @var array
31
     */
32
    private $dispatcher = [
33
        'matchClosure' => 'JetFire\Routing\Dispatcher\ClosureDispatcher',
34
        'matchController' => 'JetFire\Routing\Dispatcher\ControllerDispatcher',
35
        'matchTemplate' => 'JetFire\Routing\Dispatcher\TemplateDispatcher',
36
    ];
37
38
    /**
39
     * @param Router $router
40
     */
41
    public function __construct(Router $router)
42
    {
43
        $this->router = $router;
44
    }
45
46
    /**
47
     * @param string $matcher
48
     */
49
    public function addMatcher($matcher){
50
        $this->matcher[] = $matcher;
51
    }
52
53
    /**
54
     * @return array
55
     */
56
    public function getMatcher()
57
    {
58
        return $this->matcher;
59
    }
60
61
    /**
62
     * @param array $dispatcher
63
     */
64
    public function setDispatcher($dispatcher = []){
65
        $this->dispatcher = $dispatcher;
66
    }
67
68
    /**
69
     * @param $method
70
     * @param $class
71
     * @return mixed|void
72
     * @internal param array $dispatcher
73
     */
74
    public function addDispatcher($method,$class){
75
        $this->dispatcher[$method] = $class;
76
    }
77
78
    /**
79
     * @return bool
80
     */
81
    public function match()
82
    {
83
        $this->request = [];
84
        for ($i = 0; $i < $this->router->collection->countRoutes; ++$i) {
85
            $this->request['prefix'] = ($this->router->collection->getRoutes('prefix_' . $i) != '') ? $this->router->collection->getRoutes('prefix_' . $i) : '';
86
            foreach ($this->router->collection->getRoutes('routes_' . $i) as $route => $params) {
87
                $this->request['params'] = $params;
88
                $this->request['collection_index'] = $i;
89
                $this->request['route'] = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], '/' . trim(trim($this->request['prefix'], '/') . '/' . trim($route, '/'), '/'));
90
                if ($this->routeMatch('#^' . $this->request['route'] . '$#')) {
91
                    $this->setRoute();
92
                    return $this->generateTarget();
93
                }
94
            }
95
        }
96
        unset($this->request);
97
        return false;
98
    }
99
100
    /**
101
     * @param $match
102
     * @return string
103
     */
104
    private function paramMatch($match)
105
    {
106
        if (is_array($this->request['params']) && isset($this->request['params']['arguments'][$match[1]])) {
107
            $this->request['params']['arguments'][$match[1]] = str_replace('(', '(?:', $this->request['params']['arguments'][$match[1]]);
108
            return '(' . $this->request['params']['arguments'][$match[1]] . ')';
109
        }
110
        return '([^/]+)';
111
    }
112
113
    /**
114
     * @param $regex
115
     * @return bool
116
     */
117
    private function routeMatch($regex)
118
    {
119
        if (substr($this->request['route'], -1) == '*') {
120
            $pos = strpos($this->request['route'], '*');
121 View Code Duplication
            if (substr($this->router->route->getUrl(), 0, $pos) == substr($this->request['route'], 0, $pos))
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...
122
                if (isset($this->request)) return true;
123
        }
124 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...
125
            array_shift($this->request['parameters']);
126
            return true;
127
        }
128
        return false;
129
    }
130
131
    /**
132
     * @return bool
133
     */
134
    private function generateTarget()
135
    {
136
        if($this->validMethod()) {
137
            foreach($this->matcher as $match) if(call_user_func([$this,$match])) break;
138
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
139
            $this->router->route->addTarget('block',$this->router->collection->getRoutes('block_'.$index));
140
            $this->router->route->addTarget('view_dir',$this->router->collection->getRoutes('view_dir_'.$index));
141
            $this->router->response->setStatusCode(202);
142
        }else
143
            $this->router->response->setStatusCode(405);
144
        return $this->router->route->hasTarget();
145
    }
146
147
    /**
148
     *
149
     */
150
    private function setRoute(){
151
        if (isset($this->request['params'])) {
152
            if(is_callable($this->request['params']))
153
                $this->router->route->setCallback($this->request['params']);
154
            else {
155
                (is_array($this->request['params']) && isset($this->request['params']['use']))
156
                    ? $this->router->route->setCallback($this->request['params']['use'])
157
                    : $this->router->route->setCallback($this->request['params']);
158
                if (isset($this->request['params']['name'])) $this->router->route->setName($this->request['params']['name']);
159
                if (isset($this->request['params']['method'])) $this->request['params']['method'] = is_array($this->request['params']['method']) ? $this->request['params']['method'] : [$this->request['params']['method']];
160
            }
161
        }
162
        $this->router->route->setDetail($this->request);
163
    }
164
165
    /**
166
     * @return bool
167
     */
168
    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...
169
    {
170
        if(is_callable($this->request['params']))return true;
171
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
172
            return (isset($this->request['params']['ajax']) && $this->request['params']['ajax'] === true) ? true : false;
173
        $method = (isset($this->router->route->getDetail()['params']['method'])) ? $this->router->route->getDetail()['params']['method'] : ['GET'];
174
        return (in_array($this->router->route->getMethod(), $method)) ? true : false;
175
    }
176
177
178
    /**
179
     * @return bool
180
     */
181
    public function matchClosure()
182
    {
183
        if (is_callable($this->router->route->getCallback())) {
184
            $this->router->route->setTarget([
185
                'dispatcher' => $this->dispatcher['matchClosure'],
186
                'closure' => $this->router->route->getCallback()
187
            ]);
188
            return true;
189
        }
190
        return false;
191
    }
192
193
    /**
194
     * @return bool
195
     * @throws \Exception
196
     */
197
    public function matchController()
198
    {
199
        if (strpos($this->router->route->getCallback(), '@') !== false) {
200
            $routes = explode('@', $this->router->route->getCallback());
201
            if (!isset($routes[1])) $routes[1] = 'index';
202
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
203
            $class = (class_exists($routes[0]))
204
                ? $routes[0]
205
                : $this->router->collection->getRoutes()['ctrl_namespace_'.$index].$routes[0];
206
            if (!class_exists($class))
207
                throw new \Exception('Class "' . $class . '." is not found');
208
            if (method_exists($class, $routes[1])) {
209
                $this->router->route->setTarget([
210
                    'dispatcher' => $this->dispatcher['matchController'],
211
                    'di' => $this->router->getConfig()['di'],
212
                    'controller' => $class,
213
                    'action' => $routes[1]
214
                ]);
215
                return true;
216
            }
217
            throw new \Exception('The required method "' . $routes[1] . '" is not found in "' . $class . '"');
218
        }
219
        return false;
220
    }
221
222
    /**
223
     * @return bool
224
     * @throws \Exception
225
     */
226
    public function matchTemplate()
227
    {
228
        if(is_string($this->router->route->getCallback())) {
229
            $path = trim($this->router->route->getCallback(), '/');
230
            $extension = substr(strrchr($path, "."), 1);
231
            $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0;
232
            $viewDir = $this->router->collection->getRoutes('view_dir_' . $index);
233
            $target = null;
234
            if (in_array('.' . $extension, $this->router->getConfig()['templateExtension']) && is_file($viewDir . $path))
235
                $target = $viewDir . $path;
236
            else {
237
                foreach ($this->router->getConfig()['templateExtension'] as $ext) {
238
                    if (is_file($viewDir . $path . $ext)) {
239
                        $target = $viewDir . $path . $ext;
240
                        $extension = substr(strrchr($ext, "."), 1);
241
                        break;
242
                    }
243
                }
244
            }
245
            if(is_null($target))
246
                throw new \Exception('Template file "' . $path . '" is not found in "' . $viewDir . '"');
247
            $this->router->route->setTarget([
248
                'dispatcher' => $this->dispatcher['matchTemplate'],
249
                'template'   => $target,
250
                'extension'  => $extension,
251
                'callback'   => $this->router->getConfig()['templateCallback']
252
            ]);
253
            return true;
254
        }
255
        return false;
256
    }
257
}
258