Completed
Pull Request — master (#19)
by Flo
02:42
created

Dispatcher::handleFormRequest()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 3
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Dispatcher::sendJsFileHeader() 0 6 1
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\Event\Observer;
10
use Faulancer\Event\Type\OnDispatch;
11
use Faulancer\Exception\ClassNotFoundException;
12
use Faulancer\Exception\DispatchFailureException;
13
use Faulancer\Exception\IncompatibleResponseException;
14
use Faulancer\Http\JsonResponse;
15
use Faulancer\Http\Request;
16
use Faulancer\Http\Response;
17
use Faulancer\Exception\MethodNotFoundException;
18
use Faulancer\Service\AuthenticatorPlugin;
19
use Faulancer\Service\AuthenticatorService;
20
use Faulancer\Service\Config;
21
use Faulancer\Service\SessionManagerService;
22
use Faulancer\ServiceLocator\ServiceLocator;
23
24
/**
25
 * Class Dispatcher
26
 */
27
class Dispatcher
28
{
29
30
    /**
31
     * The current request object
32
     *
33
     * @var Request
34
     */
35
    protected $request;
36
37
    /**
38
     * The configuration object
39
     *
40
     * @var Config
41
     */
42
    protected $config;
43
44
    /**
45
     * user, api
46
     *
47
     * @var string
48
     */
49
    protected $requestType = 'default';
50
51
    /**
52
     * Dispatcher constructor.
53
     *
54
     * @param Request $request
55
     * @param Config  $config
56
     */
57
    public function __construct(Request $request, Config $config)
58
    {
59
        $this->request = $request;
60
        $this->config  = $config;
61
    }
62
63
    /**
64
     * Bootstrap for every route call
65
     *
66
     * @return Response|JsonResponse|mixed
67
     * @throws MethodNotFoundException
68
     * @throws ClassNotFoundException
69
     * @throws DispatchFailureException
70
     * @throws IncompatibleResponseException
71
     */
72
    public function dispatch()
73
    {
74
        // Check for core assets path
75
        if ($assets = $this->resolveAssetsPath()) {
76
            return $assets;
77
        }
78
79
        Observer::instance()->trigger(new OnDispatch($this));
80
81
        $this->_setLanguageFromUri();
82
83
        if (strpos($this->request->getPath(), '/api') === 0) {
84
            $this->requestType = 'api';
85
        }
86
87
        list($class, $action, $permission, $payload) = $this->getRoute($this->request->getPath());
88
89
        /** @var AbstractController $class */
90
        $class   = new $class($this->request);
91
92
        if (!empty($permission)) {
93
94
            /** @var AuthenticatorService $authenticator */
95
            $authenticator = ServiceLocator::instance()->get(AuthenticatorService::class);
96
            $isPermitted   = $authenticator->isPermitted($permission);
97
98
            if ($isPermitted === null) {
99
100
                $class->redirect($this->config->get('auth:authUrl'));
101
102
            } else if ($isPermitted === false) {
103
104
                $errorController = $this->config->get('customErrorController');
105
                return (new $errorController($this->request))->notPermittedAction();
106
107
            }
108
109
        }
110
111
        if (!method_exists($class, $action)) {
112
113
            throw new MethodNotFoundException(
114
                'Class "' . get_class($class) . '" doesn\'t have the method ' . $action
115
            );
116
117
        }
118
119
        $payload = array_map('strip_tags', $payload);
120
        $payload = array_map('htmlspecialchars', $payload);
121
122
        $response = call_user_func_array([$class, $action], $payload);
123
124
        if (!$response instanceof Response) {
125
            throw new IncompatibleResponseException('No valid response returned.');
126
        }
127
128
        return $response;
129
130
    }
131
132
    /**
133
     * @return bool
134
     */
135
    private function _setLanguageFromUri()
136
    {
137
        if ($this->request->getParam('lang') !== null) {
138
139
            $serviceLocator = ServiceLocator::instance();
140
141
            /** @var SessionManagerService $sessionManager */
142
            $sessionManager = $serviceLocator->get(SessionManagerService::class);
143
            $sessionManager->set('language', $this->request->getParam('lang'));
144
145
            return true;
146
147
        }
148
149
        return false;
150
    }
151
152
    /**
153
     * @return bool|string
154
     */
155
    private function resolveAssetsPath()
156
    {
157
        $matches = [];
158
159
        if (preg_match('/(?<style>css)|(?<script>js)/', $this->request->getPath(), $matches)) {
160
161
            $file = $this->request->getPath();
162
163
            if (strpos($file, 'core') !== false) {
164
165
                $path = str_replace('/core', '', $file);
166
167
                if ($matches['style'] === 'css') {
168
                    return $this->sendCssFileHeader($path);
169
                } else if ($matches['script'] === 'js') {
170
                    return $this->sendJsFileHeader($path);
171
                }
172
173
            }
174
175
        }
176
177
        return false;
178
    }
179
180
    /**
181
     * @param $file
182
     * @return string
183
     * @codeCoverageIgnore
184
     */
185
    public function sendCssFileHeader($file)
186
    {
187
        header('Content-Type: text/css');
188
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
189
        exit(0);
190
    }
191
192
    /**
193
     * @param $file
194
     * @return string
195
     * @codeCoverageIgnore
196
     */
197
    public function sendJsFileHeader($file)
198
    {
199
        header('Content-Type: text/javascript');
200
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
201
        exit(0);
202
    }
203
204
    /**
205
     * Get data for specific route path
206
     *
207
     * @param string $path
208
     *
209
     * @return array
210
     * @throws MethodNotFoundException
211
     */
212
    private function getRoute($path)
213
    {
214
        if (strpos($this->request->getPath(), '/api') === 0) {
215
            $routes = $this->config->get('routes:rest');
216
        } else {
217
            $routes = $this->config->get('routes');
218
        }
219
220
        foreach ($routes as $name => $data) {
221
222
            if ($target = $this->getDirectMatch($path, $data)) {
223
                return $target;
224
            } else if ($target = $this->getVariableMatch($path, $data)) {
225
                return $target;
226
            }
227
228
        }
229
230
        throw new MethodNotFoundException('No matching route for path "' . $path . '" found');
231
    }
232
233
    /**
234
     * Determines if we have a direct/static route match
235
     *
236
     * @param string $uri  The request uri
237
     * @param array  $data The result from ClassParser
238
     *
239
     * @return array
240
     * @throws MethodNotFoundException
241
     */
242
    private function getDirectMatch($uri, array $data) :array
243
    {
244
        if (!empty($data['path']) && $uri === $data['path']) {
245
246 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...
247
248
                return [
249
                    $data['controller'],
250
                    $data['action'] . 'Action',
251
                    $data['permission'] ?? null,
252
                    []
253
                ];
254
255
            } else if ($this->requestType === 'api') {
256
257
                return [
258
                    $data['controller'],
259
                    $this->getRestfulAction(),
260
                    $data['permission'] ?? null,
261
                    []
262
                ];
263
264
            }
265
266
            throw new MethodNotFoundException('Non valid request method available.');
267
268
        }
269
270
        return [];
271
    }
272
273
    /**
274
     * @param string $uri
275
     * @param array  $data
276
     * @return array
277
     */
278
    /*
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...
279
    private function getVariableMatch(string $uri, array $data)
280
    {
281
        if (empty($data['path']) || strpos($data['path'], '[') === false || $data['path'] === '/') {
282
            return [];
283
        }
284
285
        $vars  = [];
286
        $regex = '/(?<parts>(\[:?[\w\d]+\]))/';
287
288
        $pathRegex = preg_replace($regex, '(.*)', $data['path']);
289
        $pathRegex = str_replace('/', '\/', $pathRegex);
290
291
        if (preg_match('/' . $pathRegex . '/', $uri, $vars)) {
292
293
            array_splice($vars, 0, 1);
294
295
            if ($this->requestType === 'default' && in_array($this->request->getMethod(), $data['method'])) {
296
297
                return [
298
                    $data['controller'],
299
                    $data['action'] . 'Action',
300
                    $data['permission'] ?? null,
301
                    $vars
302
                ];
303
304
            } else if ($this->requestType === 'api') {
305
306
                return [
307
                    $data['controller'],
308
                    $this->getRestfulAction(),
309
                    $data['permission'] ?? null,
310
                    $vars
311
                ];
312
313
            }
314
315
        }
316
317
        return [];
318
319
    }*/
320
321
    /**
322
     * Determines if we have a variable route match
323
     *
324
     * @param string $uri
325
     * @param array  $data
326
     *
327
     * @return array
328
     * @throws MethodNotFoundException
329
     */
330
331
    private function getVariableMatch($uri, array $data) :array
332
    {
333
        if (empty($data['path']) || $data['path'] === '/') {
334
            return [];
335
        }
336
337
        $var   = [];
338
        $regex = str_replace(['/', '___'], ['\/', '+'], $data['path']);
339
340
        if (preg_match('|^' . $regex . '$|', $uri, $var)) {
341
342
            array_splice($var, 0, 1);
343
344 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...
345
346
                return [
347
                    $data['controller'],
348
                    $data['action'] . 'Action',
349
                    $data['permission'] ?? null,
350
                    $var
351
                ];
352
353
            } else if ($this->requestType === 'api') {
354
355
                return [
356
                    $data['controller'],
357
                    $this->getRestfulAction(),
358
                    $data['permission'] ?? null,
359
                    $var
360
                ];
361
362
            }
363
364
        }
365
366
        return [];
367
    }
368
    
369
370
    /**
371
     * @return string
372
     */
373
    private function getRestfulAction()
374
    {
375
        $method = strtoupper($this->request->getMethod());
376
377
        switch ($method) {
378
379
            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...
380
                return 'get';
381
382
            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...
383
                return 'create';
384
385
            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...
386
                return 'update';
387
388
            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...
389
                return 'delete';
390
391
            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...
392
                return 'update';
393
394
            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...
395
                return 'get';
396
397
        }
398
    }
399
400
}