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

Dispatcher::dispatch()   C

Complexity

Conditions 7
Paths 33

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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