Test Failed
Pull Request — master (#19)
by Flo
04:39
created

Dispatcher::dispatch()   C

Complexity

Conditions 7
Paths 25

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 17
nc 25
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
19
/**
20
 * Class Dispatcher
21
 */
22
class Dispatcher
23
{
24
25
    /**
26
     * The current request object
27
     *
28
     * @var Request
29
     */
30
    protected $request;
31
32
    /**
33
     * The configuration object
34
     *
35
     * @var Config
36
     */
37
    protected $config;
38
39
    /**
40
     * user, api
41
     *
42
     * @var string
43
     */
44
    protected $requestType = 'default';
45
46
    /**
47
     * Dispatcher constructor.
48
     *
49
     * @param Request $request
50
     * @param Config  $config
51
     */
52
    public function __construct(Request $request, Config $config)
53
    {
54
        $this->request = $request;
55
        $this->config  = $config;
56
    }
57
58
    /**
59
     * Bootstrap for every route call
60
     *
61
     * @return Response|JsonResponse|mixed
62
     * @throws MethodNotFoundException
63
     * @throws ClassNotFoundException
64
     * @throws DispatchFailureException
65
     * @throws IncompatibleResponseException
66
     */
67
    public function dispatch()
68
    {
69
        // Check for core assets path
70
        if ($assets = $this->resolveAssetsPath()) {
71
            return $assets;
72
        }
73
74
        /** @var Response $response */
75
        $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...
76
77
        if (strpos($this->request->getUri(), '/api') === 0) {
78
            $this->requestType = 'api';
79
        }
80
81
        $target  = $this->getRoute($this->request->getUri());
82
        $class   = $target['class'];
83
        $action  = $this->requestType === 'api' ? $this->getRestfulAction() : $target['action'];
84
        $payload = !empty($target['var']) ? $target['var'] : [];
85
86
        /** @var Response $class */
87
        $class = new $class($this->request);
88
89
        if (!method_exists($class, $action)) {
90
            throw new MethodNotFoundException('Class "' . get_class($class) . '" doesn\'t have the method ' . $action);
91
        }
92
93
        $response = call_user_func_array([$class, $action], $payload);
94
95
        if (!$response instanceof Response) {
96
            throw new IncompatibleResponseException('No valid response returned.');
97
        }
98
99
        return $response;
100
101
    }
102
103
    /**
104
     * @return bool|string
105
     */
106
    private function resolveAssetsPath()
107
    {
108
        $matches = [];
109
110
        if (preg_match('/(?<style>css)|(?<script>js)/', $this->request->getUri(), $matches)) {
111
112
            $file = $this->request->getUri();
113
114
            if (strpos($file, 'core') !== false) {
115
116
                $path = str_replace('/core', '', $file);
117
118
                if ($matches['style'] === 'css') {
119
                    return $this->sendCssFileHeader($path);
120
                } else if ($matches['script'] === 'js') {
121
                    return $this->sendJsFileHeader($path);
122
                }
123
124
            }
125
126
        }
127
128
        return false;
129
    }
130
131
    /**
132
     * @param $file
133
     * @return string
134
     * @codeCoverageIgnore
135
     */
136
    public function sendCssFileHeader($file)
137
    {
138
        header('Content-Type: text/css');
139
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
140
        exit(0);
141
    }
142
143
    /**
144
     * @param $file
145
     * @return string
146
     * @codeCoverageIgnore
147
     */
148
    public function sendJsFileHeader($file)
149
    {
150
        header('Content-Type: text/javascript');
151
        echo file_get_contents(__DIR__ . '/../../public/assets' . $file);
152
        exit(0);
153
    }
154
155
    /**
156
     * Get data for specific route path
157
     *
158
     * @param string $path
159
     *
160
     * @return array
161
     * @throws MethodNotFoundException
162
     */
163
    private function getRoute($path)
164
    {
165
        if (strpos($this->request->getUri(), '/api') === 0) {
166
            $routes = $this->config->get('routes:rest');
167
        } else {
168
            $routes = $this->config->get('routes');
169
        }
170
171
        foreach ($routes as $name => $data) {
172
173
            if ($target = $this->getDirectMatch($path, $data)) {
174
                return $target;
175
            } else if ($target = $this->getVariableMatch($path, $data)) {
176
                return $target;
177
            }
178
179
        }
180
181
        throw new MethodNotFoundException('No matching route for path "' . $path . '" found');
182
    }
183
184
    /**
185
     * Determines if we have a direct/static route match
186
     *
187
     * @param string $uri  The request uri
188
     * @param array  $data The result from ClassParser
189
     *
190
     * @return array
191
     * @throws MethodNotFoundException
192
     */
193
    private function getDirectMatch($uri, array $data) :array
194
    {
195
        if (!empty($data['path']) && $uri === $data['path']) {
196
197
            if ($this->requestType === 'default' && in_array($this->request->getMethod(), $data['method'])) {
198
199
                return [
200
                    'class'  => $data['controller'],
201
                    'action' => $data['action'] . 'Action'
202
                ];
203
204 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...
205
206
                return [
207
                    'class'  => $data['controller'],
208
                    'action' => $this->getRestfulAction()
209
                ];
210
211
            }
212
213
            throw new MethodNotFoundException('Non valid request method available.');
214
215
        }
216
217
        return [];
218
    }
219
220
    /**
221
     * Determines if we have a variable route match
222
     *
223
     * @param string $uri
224
     * @param array  $data
225
     *
226
     * @return array
227
     * @throws MethodNotFoundException
228
     */
229
    private function getVariableMatch($uri, array $data) :array
230
    {
231
        if (empty($data['path']) || $data['path'] === '/') {
232
            return [];
233
        }
234
235
        $var   = [];
236
        $regex = str_replace(['/', '___'], ['\/', '+'], $data['path']);
237
238
        if (preg_match('|^' . $regex . '$|', $uri, $var)) {
239
240
            array_splice($var, 0, 1);
241
242
            if ($this->requestType === 'default'  && in_array($this->request->getMethod(), $data['method'])) {
243
244
                return [
245
                    'class'  => $data['controller'],
246
                    'action' => $data['action'] . 'Action',
247
                    'var'    => $var
248
                ];
249
250 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...
251
252
                return [
253
                    'class'  => $data['controller'],
254
                    'action' => $this->getRestfulAction(),
255
                    'var'    => $var
256
                ];
257
258
            }
259
260
        }
261
262
        return [];
263
    }
264
265
    /**
266
     * @return string
267
     */
268
    private function getRestfulAction()
269
    {
270
        $method = strtoupper($this->request->getMethod());
271
272
        switch ($method) {
273
274
            case 'GET':
275
                return 'get';
276
277
            case 'POST':
278
                return 'create';
279
280
            case 'PUT':
281
                return 'update';
282
283
            case 'DELETE':
284
                return 'delete';
285
286
            case 'PATCH':
287
                return 'update';
288
289
            default:
290
                return 'get';
291
292
        }
293
    }
294
295
}