Test Failed
Pull Request — master (#19)
by Flo
02:33
created

Dispatcher::dispatch()   C

Complexity

Conditions 8
Paths 49

Size

Total Lines 45
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 45
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 24
nc 49
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\Form\AbstractFormHandler;
13
use Faulancer\Http\JsonResponse;
14
use Faulancer\Http\Request;
15
use Faulancer\Http\Response;
16
use Faulancer\Exception\MethodNotFoundException;
17
use Faulancer\Service\Config;
18
use Faulancer\Service\SessionManagerService;
19
use Faulancer\ServiceLocator\ServiceLocator;
20
use Faulancer\Session\SessionManager;
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
        /** @var Response $response */
78
        $response = null;
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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