Test Failed
Pull Request — master (#19)
by Flo
03:16
created

Dispatcher   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 372
Duplicated Lines 10.22 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 8
dl 38
loc 372
rs 8.295
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B dispatch() 0 57 8
A _setLanguageFromUri() 0 16 2
B resolveAssetsPath() 0 24 5
A sendCssFileHeader() 0 6 1
A sendJsFileHeader() 0 6 1
B getRoute() 0 20 5
B getDirectMatch() 19 30 6
C getVariableMatch() 19 37 7
B getRestfulAction() 0 26 6

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 Dispatcher 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 Dispatcher, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Class Dispatcher | Dispatcher.php
4
 * @package Faulancer\AbstractController
5
 * @author Florian Knapp <[email protected]>
6
 */
7
namespace Faulancer\Controller;
8
9
use Faulancer\Exception\ClassNotFoundException;
10
use Faulancer\Exception\DispatchFailureException;
11
use Faulancer\Exception\IncompatibleResponseException;
12
use Faulancer\Http\JsonResponse;
13
use Faulancer\Http\Request;
14
use Faulancer\Http\Response;
15
use Faulancer\Exception\MethodNotFoundException;
16
use Faulancer\Service\AuthenticatorPlugin;
17
use Faulancer\Service\AuthenticatorService;
18
use Faulancer\Service\Config;
19
use Faulancer\Service\SessionManagerService;
20
use Faulancer\ServiceLocator\ServiceLocator;
21
22
/**
23
 * Class Dispatcher
24
 */
25
class Dispatcher
26
{
27
28
    /**
29
     * The current request object
30
     *
31
     * @var Request
32
     */
33
    protected $request;
34
35
    /**
36
     * The configuration object
37
     *
38
     * @var Config
39
     */
40
    protected $config;
41
42
    /**
43
     * user, api
44
     *
45
     * @var string
46
     */
47
    protected $requestType = 'default';
48
49
    /**
50
     * Dispatcher constructor.
51
     *
52
     * @param Request $request
53
     * @param Config  $config
54
     */
55
    public function __construct(Request $request, Config $config)
56
    {
57
        $this->request = $request;
58
        $this->config  = $config;
59
    }
60
61
    /**
62
     * Bootstrap for every route call
63
     *
64
     * @return Response|JsonResponse|mixed
65
     * @throws MethodNotFoundException
66
     * @throws ClassNotFoundException
67
     * @throws DispatchFailureException
68
     * @throws IncompatibleResponseException
69
     */
70
    public function dispatch()
71
    {
72
        // Check for core assets path
73
        if ($assets = $this->resolveAssetsPath()) {
74
            return $assets;
75
        }
76
77
        $this->_setLanguageFromUri();
78
79
        if (strpos($this->request->getPath(), '/api') === 0) {
80
            $this->requestType = 'api';
81
        }
82
83
        list($class, $action, $permission, $payload) = $this->getRoute($this->request->getPath());
84
85
        /** @var AbstractController $class */
86
        $class   = new $class($this->request);
87
88
        if (!empty($permission)) {
89
90
            /** @var AuthenticatorService $authenticator */
91
            $authenticator = ServiceLocator::instance()->get(AuthenticatorService::class);
92
            $isPermitted   = $authenticator->isPermitted($permission);
93
94
            if ($isPermitted === null) {
95
96
                $class->redirect($this->config->get('auth:authUrl'));
97
98
            } else if ($isPermitted === false) {
99
100
                $errorController = $this->config->get('customErrorController');
101
                return (new $errorController($this->request))->notPermittedAction();
102
103
            }
104
105
        }
106
107
        if (!method_exists($class, $action)) {
108
109
            throw new MethodNotFoundException(
110
                'Class "' . get_class($class) . '" doesn\'t have the method ' . $action
111
            );
112
113
        }
114
115
        $payload = array_map('strip_tags', $payload);
116
        $payload = array_map('htmlspecialchars', $payload);
117
118
        $response = call_user_func_array([$class, $action], $payload);
119
120
        if (!$response instanceof Response) {
121
            throw new IncompatibleResponseException('No valid response returned.');
122
        }
123
124
        return $response;
125
126
    }
127
128
    /**
129
     * @return bool
130
     */
131
    private function _setLanguageFromUri()
132
    {
133
        if ($this->request->getParam('lang') !== null) {
134
135
            $serviceLocator = ServiceLocator::instance();
136
137
            /** @var SessionManagerService $sessionManager */
138
            $sessionManager = $serviceLocator->get(SessionManagerService::class);
139
            $sessionManager->set('language', $this->request->getParam('lang'));
140
141
            return true;
142
143
        }
144
145
        return false;
146
    }
147
148
    /**
149
     * @return bool|string
150
     */
151
    private function resolveAssetsPath()
152
    {
153
        $matches = [];
154
155
        if (preg_match('/(?<style>css)|(?<script>js)/', $this->request->getPath(), $matches)) {
156
157
            $file = $this->request->getPath();
158
159
            if (strpos($file, 'core') !== false) {
160
161
                $path = str_replace('/core', '', $file);
162
163
                if ($matches['style'] === 'css') {
164
                    return $this->sendCssFileHeader($path);
165
                } else if ($matches['script'] === 'js') {
166
                    return $this->sendJsFileHeader($path);
167
                }
168
169
            }
170
171
        }
172
173
        return false;
174
    }
175
176
    /**
177
     * @param $file
178
     * @return string
179
     * @codeCoverageIgnore
180
     */
181
    public function sendCssFileHeader($file)
182
    {
183
        header('Content-Type: text/css');
184
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
185
        exit(0);
186
    }
187
188
    /**
189
     * @param $file
190
     * @return string
191
     * @codeCoverageIgnore
192
     */
193
    public function sendJsFileHeader($file)
194
    {
195
        header('Content-Type: text/javascript');
196
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
197
        exit(0);
198
    }
199
200
    /**
201
     * Get data for specific route path
202
     *
203
     * @param string $path
204
     *
205
     * @return array
206
     * @throws MethodNotFoundException
207
     */
208
    private function getRoute($path)
209
    {
210
        if (strpos($this->request->getPath(), '/api') === 0) {
211
            $routes = $this->config->get('routes:rest');
212
        } else {
213
            $routes = $this->config->get('routes');
214
        }
215
216
        foreach ($routes as $name => $data) {
217
218
            if ($target = $this->getDirectMatch($path, $data)) {
219
                return $target;
220
            } else if ($target = $this->getVariableMatch($path, $data)) {
221
                return $target;
222
            }
223
224
        }
225
226
        throw new MethodNotFoundException('No matching route for path "' . $path . '" found');
227
    }
228
229
    /**
230
     * Determines if we have a direct/static route match
231
     *
232
     * @param string $uri  The request uri
233
     * @param array  $data The result from ClassParser
234
     *
235
     * @return array
236
     * @throws MethodNotFoundException
237
     */
238
    private function getDirectMatch($uri, array $data) :array
239
    {
240
        if (!empty($data['path']) && $uri === $data['path']) {
241
242 View Code Duplication
            if ($this->requestType === 'default' && in_array($this->request->getMethod(), $data['method'])) {
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...
243
244
                return [
245
                    $data['controller'],
246
                    $data['action'] . 'Action',
247
                    $data['permission'] ?? null,
248
                    []
249
                ];
250
251
            } else if ($this->requestType === 'api') {
252
253
                return [
254
                    $data['controller'],
255
                    $this->getRestfulAction(),
256
                    $data['permission'] ?? null,
257
                    []
258
                ];
259
260
            }
261
262
            throw new MethodNotFoundException('Non valid request method available.');
263
264
        }
265
266
        return [];
267
    }
268
269
    /**
270
     * @param string $uri
271
     * @param array  $data
272
     * @return array
273
     */
274
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
275
    private function getVariableMatch(string $uri, array $data)
276
    {
277
        if (empty($data['path']) || strpos($data['path'], '[') === false || $data['path'] === '/') {
278
            return [];
279
        }
280
281
        $vars  = [];
282
        $regex = '/(?<parts>(\[:?[\w\d]+\]))/';
283
284
        $pathRegex = preg_replace($regex, '(.*)', $data['path']);
285
        $pathRegex = str_replace('/', '\/', $pathRegex);
286
287
        if (preg_match('/' . $pathRegex . '/', $uri, $vars)) {
288
289
            array_splice($vars, 0, 1);
290
291
            if ($this->requestType === 'default' && in_array($this->request->getMethod(), $data['method'])) {
292
293
                return [
294
                    $data['controller'],
295
                    $data['action'] . 'Action',
296
                    $data['permission'] ?? null,
297
                    $vars
298
                ];
299
300
            } else if ($this->requestType === 'api') {
301
302
                return [
303
                    $data['controller'],
304
                    $this->getRestfulAction(),
305
                    $data['permission'] ?? null,
306
                    $vars
307
                ];
308
309
            }
310
311
        }
312
313
        return [];
314
315
    }*/
316
317
    /**
318
     * Determines if we have a variable route match
319
     *
320
     * @param string $uri
321
     * @param array  $data
322
     *
323
     * @return array
324
     * @throws MethodNotFoundException
325
     */
326
327
    private function getVariableMatch($uri, array $data) :array
328
    {
329
        if (empty($data['path']) || $data['path'] === '/') {
330
            return [];
331
        }
332
333
        $var   = [];
334
        $regex = str_replace(['/', '___'], ['\/', '+'], $data['path']);
335
336
        if (preg_match('|^' . $regex . '$|', $uri, $var)) {
337
338
            array_splice($var, 0, 1);
339
340 View Code Duplication
            if ($this->requestType === 'default'  && in_array($this->request->getMethod(), $data['method'])) {
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...
341
342
                return [
343
                    $data['controller'],
344
                    $data['action'] . 'Action',
345
                    $data['permission'] ?? null,
346
                    $var
347
                ];
348
349
            } else if ($this->requestType === 'api') {
350
351
                return [
352
                    $data['controller'],
353
                    $this->getRestfulAction(),
354
                    $data['permission'] ?? null,
355
                    $var
356
                ];
357
358
            }
359
360
        }
361
362
        return [];
363
    }
364
    
365
366
    /**
367
     * @return string
368
     */
369
    private function getRestfulAction()
370
    {
371
        $method = strtoupper($this->request->getMethod());
372
373
        switch ($method) {
374
375
            case 'GET':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
376
                return 'get';
377
378
            case 'POST':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
379
                return 'create';
380
381
            case 'PUT':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
382
                return 'update';
383
384
            case 'DELETE':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
385
                return 'delete';
386
387
            case 'PATCH':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
388
                return 'update';
389
390
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
391
                return 'get';
392
393
        }
394
    }
395
396
}