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

Dispatcher::run()   B

Complexity

Conditions 4
Paths 9

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 16
nc 9
nop 0
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
            throw new MethodNotFoundException('Class "' . get_class($class) . '" doesn\'t have the method ' . $action);
109
        }
110
111
        $payload = array_map('strip_tags', $payload);
112
        $payload = array_map('htmlspecialchars', $payload);
113
114
        $response = call_user_func_array([$class, $action], $payload);
115
116
        if (!$response instanceof Response) {
117
            throw new IncompatibleResponseException('No valid response returned.');
118
        }
119
120
        return $response;
121
122
    }
123
124
    /**
125
     * @return bool
126
     */
127
    private function _setLanguageFromUri()
128
    {
129
        if ($this->request->getParam('lang') !== null) {
130
131
            $serviceLocator = ServiceLocator::instance();
132
133
            /** @var SessionManagerService $sessionManager */
134
            $sessionManager = $serviceLocator->get(SessionManagerService::class);
135
            $sessionManager->set('language', $this->request->getParam('lang'));
136
137
            return true;
138
139
        }
140
141
        return false;
142
    }
143
144
    /**
145
     * @return bool|string
146
     */
147
    private function resolveAssetsPath()
148
    {
149
        $matches = [];
150
151
        if (preg_match('/(?<style>css)|(?<script>js)/', $this->request->getPath(), $matches)) {
152
153
            $file = $this->request->getPath();
154
155
            if (strpos($file, 'core') !== false) {
156
157
                $path = str_replace('/core', '', $file);
158
159
                if ($matches['style'] === 'css') {
160
                    return $this->sendCssFileHeader($path);
161
                } else if ($matches['script'] === 'js') {
162
                    return $this->sendJsFileHeader($path);
163
                }
164
165
            }
166
167
        }
168
169
        return false;
170
    }
171
172
    /**
173
     * @param $file
174
     * @return string
175
     * @codeCoverageIgnore
176
     */
177
    public function sendCssFileHeader($file)
178
    {
179
        header('Content-Type: text/css');
180
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
181
        exit(0);
182
    }
183
184
    /**
185
     * @param $file
186
     * @return string
187
     * @codeCoverageIgnore
188
     */
189
    public function sendJsFileHeader($file)
190
    {
191
        header('Content-Type: text/javascript');
192
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
193
        exit(0);
194
    }
195
196
    /**
197
     * Get data for specific route path
198
     *
199
     * @param string $path
200
     *
201
     * @return array
202
     * @throws MethodNotFoundException
203
     */
204
    private function getRoute($path)
205
    {
206
        if (strpos($this->request->getPath(), '/api') === 0) {
207
            $routes = $this->config->get('routes:rest');
208
        } else {
209
            $routes = $this->config->get('routes');
210
        }
211
212
        foreach ($routes as $name => $data) {
213
214
            if ($target = $this->getDirectMatch($path, $data)) {
215
                return $target;
216
            } else if ($target = $this->getVariableMatch($path, $data)) {
217
                return $target;
218
            }
219
220
        }
221
222
        throw new MethodNotFoundException('No matching route for path "' . $path . '" found');
223
    }
224
225
    /**
226
     * Determines if we have a direct/static route match
227
     *
228
     * @param string $uri  The request uri
229
     * @param array  $data The result from ClassParser
230
     *
231
     * @return array
232
     * @throws MethodNotFoundException
233
     */
234
    private function getDirectMatch($uri, array $data) :array
235
    {
236
        if (!empty($data['path']) && $uri === $data['path']) {
237
238 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...
239
240
                return [
241
                    $data['controller'],
242
                    $data['action'] . 'Action',
243
                    $data['permission'] ?? null,
244
                    []
245
                ];
246
247
            } else if ($this->requestType === 'api') {
248
249
                return [
250
                    $data['controller'],
251
                    $this->getRestfulAction(),
252
                    $data['permission'] ?? null,
253
                    []
254
                ];
255
256
            }
257
258
            throw new MethodNotFoundException('Non valid request method available.');
259
260
        }
261
262
        return [];
263
    }
264
265
    /**
266
     * @param string $uri
267
     * @param array  $data
268
     * @return array
269
     */
270
    /*
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...
271
    private function getVariableMatch(string $uri, array $data)
272
    {
273
        if (empty($data['path']) || strpos($data['path'], '[') === false || $data['path'] === '/') {
274
            return [];
275
        }
276
277
        $vars  = [];
278
        $regex = '/(?<parts>(\[:?[\w\d]+\]))/';
279
280
        $pathRegex = preg_replace($regex, '(.*)', $data['path']);
281
        $pathRegex = str_replace('/', '\/', $pathRegex);
282
283
        if (preg_match('/' . $pathRegex . '/', $uri, $vars)) {
284
285
            array_splice($vars, 0, 1);
286
287
            if ($this->requestType === 'default' && in_array($this->request->getMethod(), $data['method'])) {
288
289
                return [
290
                    $data['controller'],
291
                    $data['action'] . 'Action',
292
                    $data['permission'] ?? null,
293
                    $vars
294
                ];
295
296
            } else if ($this->requestType === 'api') {
297
298
                return [
299
                    $data['controller'],
300
                    $this->getRestfulAction(),
301
                    $data['permission'] ?? null,
302
                    $vars
303
                ];
304
305
            }
306
307
        }
308
309
        return [];
310
311
    }*/
312
313
    /**
314
     * Determines if we have a variable route match
315
     *
316
     * @param string $uri
317
     * @param array  $data
318
     *
319
     * @return array
320
     * @throws MethodNotFoundException
321
     */
322
323
    private function getVariableMatch($uri, array $data) :array
324
    {
325
        if (empty($data['path']) || $data['path'] === '/') {
326
            return [];
327
        }
328
329
        $var   = [];
330
        $regex = str_replace(['/', '___'], ['\/', '+'], $data['path']);
331
332
        if (preg_match('|^' . $regex . '$|', $uri, $var)) {
333
334
            array_splice($var, 0, 1);
335
336 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...
337
338
                return [
339
                    $data['controller'],
340
                    $data['action'] . 'Action',
341
                    $data['permission'] ?? null,
342
                    $var
343
                ];
344
345
            } else if ($this->requestType === 'api') {
346
347
                return [
348
                    $data['controller'],
349
                    $this->getRestfulAction(),
350
                    $data['permission'] ?? null,
351
                    $var
352
                ];
353
354
            }
355
356
        }
357
358
        return [];
359
    }
360
    
361
362
    /**
363
     * @return string
364
     */
365
    private function getRestfulAction()
366
    {
367
        $method = strtoupper($this->request->getMethod());
368
369
        switch ($method) {
370
371
            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...
372
                return 'get';
373
374
            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...
375
                return 'create';
376
377
            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...
378
                return 'update';
379
380
            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...
381
                return 'delete';
382
383
            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...
384
                return 'update';
385
386
            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...
387
                return 'get';
388
389
        }
390
    }
391
392
}