Test Setup Failed
Pull Request — master (#19)
by Flo
03:33
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
        if ($this->request->getParam('lang') !== null) {
78
79
            $serviceLocator = ServiceLocator::instance();
80
81
            /** @var SessionManagerService $sessionManager */
82
            $sessionManager = $serviceLocator->get(SessionManagerService::class);
83
            $sessionManager->set('language', $this->request->getParam('lang'));
84
85
        }
86
87
        if (strpos($this->request->getUri(), '/api') === 0) {
88
            $this->requestType = 'api';
89
        }
90
91
        $target  = $this->getRoute($this->request->getUri());
92
        $class   = $target['class'];
93
        $action  = $this->requestType === 'api' ? $this->getRestfulAction() : $target['action'];
94
        $payload = !empty($target['var']) ? $target['var'] : [];
95
96
        /** @var AbstractController $class */
97
        $class   = new $class($this->request);
98
99
        $requiredPermissions = $target['permission'];
100
101
        if (!empty($requiredPermissions)) {
102
103
            /** @var AuthenticatorService $authenticator */
104
            $authenticator = ServiceLocator::instance()->get(AuthenticatorService::class);
105
            $isPermitted   = $authenticator->isPermitted($requiredPermissions);
106
107
            if ($isPermitted === null) {
108
109
                $class->redirect($this->config->get('auth:authUrl'));
110
111
            } else if ($isPermitted === false) {
112
113
                /** @var ErrorController $errorController */
114
                $errorController = $this->config->get('customErrorController');
115
                return (new $errorController($this->request))->notPermittedAction();
116
117
            }
118
119
        }
120
121
        if (!method_exists($class, $action)) {
122
            throw new MethodNotFoundException('Class "' . get_class($class) . '" doesn\'t have the method ' . $action);
123
        }
124
125
        $payload = array_map('strip_tags', $payload);
126
127
        $response = call_user_func_array([$class, $action], $payload);
128
129
        if (!$response instanceof Response) {
130
            throw new IncompatibleResponseException('No valid response returned.');
131
        }
132
133
        return $response;
134
135
    }
136
137
    /**
138
     * @return bool|string
139
     */
140
    private function resolveAssetsPath()
141
    {
142
        $matches = [];
143
144
        if (preg_match('/(?<style>css)|(?<script>js)/', $this->request->getUri(), $matches)) {
145
146
            $file = $this->request->getUri();
147
148
            if (strpos($file, 'core') !== false) {
149
150
                $path = str_replace('/core', '', $file);
151
152
                if ($matches['style'] === 'css') {
153
                    return $this->sendCssFileHeader($path);
154
                } else if ($matches['script'] === 'js') {
155
                    return $this->sendJsFileHeader($path);
156
                }
157
158
            }
159
160
        }
161
162
        return false;
163
    }
164
165
    /**
166
     * @param $file
167
     * @return string
168
     * @codeCoverageIgnore
169
     */
170
    public function sendCssFileHeader($file)
171
    {
172
        header('Content-Type: text/css');
173
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
174
        exit(0);
175
    }
176
177
    /**
178
     * @param $file
179
     * @return string
180
     * @codeCoverageIgnore
181
     */
182
    public function sendJsFileHeader($file)
183
    {
184
        header('Content-Type: text/javascript');
185
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
186
        exit(0);
187
    }
188
189
    /**
190
     * Get data for specific route path
191
     *
192
     * @param string $path
193
     *
194
     * @return array
195
     * @throws MethodNotFoundException
196
     */
197
    private function getRoute($path)
198
    {
199
        if (strpos($this->request->getUri(), '/api') === 0) {
200
            $routes = $this->config->get('routes:rest');
201
        } else {
202
            $routes = $this->config->get('routes');
203
        }
204
205
        foreach ($routes as $name => $data) {
206
207
            if ($target = $this->getDirectMatch($path, $data)) {
208
                return $target;
209
            } else if ($target = $this->getVariableMatch($path, $data)) {
210
                return $target;
211
            }
212
213
        }
214
215
        throw new MethodNotFoundException('No matching route for path "' . $path . '" found');
216
    }
217
218
    /**
219
     * Determines if we have a direct/static route match
220
     *
221
     * @param string $uri  The request uri
222
     * @param array  $data The result from ClassParser
223
     *
224
     * @return array
225
     * @throws MethodNotFoundException
226
     */
227
    private function getDirectMatch($uri, array $data) :array
228
    {
229
        if (!empty($data['path']) && $uri === $data['path']) {
230
231
            if ($this->requestType === 'default' && in_array($this->request->getMethod(), $data['method'])) {
232
233
                return [
234
                    'class'      => $data['controller'],
235
                    'action'     => $data['action'] . 'Action',
236
                    'permission' => $data['permission'] ?? null
237
                ];
238
239 View Code Duplication
            } else if ($this->requestType === 'api') {
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...
240
241
                return [
242
                    'class'      => $data['controller'],
243
                    'action'     => $this->getRestfulAction(),
244
                    'permission' => $data['permission'] ?? null
245
                ];
246
247
            }
248
249
            throw new MethodNotFoundException('Non valid request method available.');
250
251
        }
252
253
        return [];
254
    }
255
256
    /**
257
     * Determines if we have a variable route match
258
     *
259
     * @param string $uri
260
     * @param array  $data
261
     *
262
     * @return array
263
     * @throws MethodNotFoundException
264
     */
265
    private function getVariableMatch($uri, array $data) :array
266
    {
267
        if (empty($data['path']) || $data['path'] === '/') {
268
            return [];
269
        }
270
271
        $var   = [];
272
        $regex = str_replace(['/', '___'], ['\/', '+'], $data['path']);
273
274
        if (preg_match('|^' . $regex . '$|', $uri, $var)) {
275
276
            array_splice($var, 0, 1);
277
278
            if ($this->requestType === 'default'  && in_array($this->request->getMethod(), $data['method'])) {
279
280
                return [
281
                    'class'      => $data['controller'],
282
                    'action'     => $data['action'] . 'Action',
283
                    'permission' => $data['permission'] ?? null,
284
                    'var'        => $var
285
                ];
286
287 View Code Duplication
            } else if ($this->requestType === 'api') {
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...
288
289
                return [
290
                    'class'      => $data['controller'],
291
                    'action'     => $this->getRestfulAction(),
292
                    'permission' => $data['permission'] ?? null,
293
                    'var'       => $var
294
                ];
295
296
            }
297
298
        }
299
300
        return [];
301
    }
302
303
    /**
304
     * @return string
305
     */
306
    private function getRestfulAction()
307
    {
308
        $method = strtoupper($this->request->getMethod());
309
310
        switch ($method) {
311
312
            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...
313
                return 'get';
314
315
            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...
316
                return 'create';
317
318
            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...
319
                return 'update';
320
321
            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...
322
                return 'delete';
323
324
            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...
325
                return 'update';
326
327
            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...
328
                return 'get';
329
330
        }
331
    }
332
333
}