RouterCommand::sendResponse()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 14
rs 10
cc 4
nc 4
nop 1
1
<?php
2
/**
3
 *
4
 * KNUT7 K7F (https://marciozebedeu.com/)
5
 * KNUT7 K7F (tm) : Rapid Development Framework (https://marciozebedeu.com/)
6
 *
7
 * Licensed under The MIT License
8
 * For full copyright and license information, please see the LICENSE.txt
9
 * Redistributions of files must retain the above copyright notice.
10
 *
11
 * @link      https://github.com/knut7/framework/ for the canonical source repository
12
 * @copyright (c) 2015.  KNUT7  Software Technologies AO Inc. (https://marciozebedeu.com/)
13
 * @license   https://marciozebedeu.com/license/new-bsd MIT lIcense
14
 * @author    Marcio Zebedeu - [email protected]
15
 * @version   1.0.14
16
 *
17
 *
18
 */
19
20
namespace Ballybran\Routing\Router;
21
22
23
use Closure;
24
use Exception;
25
use ReflectionException;
26
use ReflectionFunction;
27
use ReflectionMethod;
28
use Reflector;
29
use Symfony\Component\HttpFoundation\Request;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\HttpFoundation\Request was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
use Symfony\Component\HttpFoundation\Response;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\HttpFoundation\Response was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
32
class RouterCommand
33
{
34
    /**
35
     * @var RouterCommand Class instance variable
36
     */
37
    protected static $instance = null;
38
39
    /**
40
     * @var string
41
     */
42
    protected $baseFolder;
43
44
    /**
45
     * @var array
46
     */
47
    protected $paths;
48
49
    /**
50
     * @var array
51
     */
52
    protected $namespaces;
53
54
    /**
55
     * @var Request
56
     */
57
    protected $request;
58
59
    /**
60
     * @var Response
61
     */
62
    protected $response;
63
64
    /**
65
     * @var array
66
     */
67
    protected $middlewares = [];
68
69
    /**
70
     * @var array
71
     */
72
    protected $markedMiddlewares = [];
73
74
    /**
75
     * RouterCommand constructor.
76
     *
77
     * @param string   $baseFolder
78
     * @param array    $paths
79
     * @param array    $namespaces
80
     * @param Request  $request
81
     * @param Response $response
82
     * @param array    $middlewares
83
     */
84
    public function __construct(
85
        string $baseFolder,
86
        array $paths,
87
        array $namespaces,
88
        Request $request,
89
        Response $response,
90
        array $middlewares
91
    ) {
92
        $this->baseFolder = $baseFolder;
93
        $this->paths = $paths;
94
        $this->namespaces = $namespaces;
95
        $this->request = $request;
96
        $this->response = $response;
97
        $this->middlewares = $middlewares;
98
99
        // Execute general Middlewares
100
        foreach ($this->middlewares['middlewares'] as $middleware) {
101
            $this->beforeAfter($middleware);
102
        }
103
104
    }
105
106
    /**
107
     * @return array
108
     */
109
    public function getMiddlewareInfo(): array
110
    {
111
        return [
112
            'path' => "{$this->baseFolder}/{$this->paths['middlewares']}",
113
            'namespace' => $this->namespaces['middlewares'],
114
        ];
115
    }
116
117
    /**
118
     * @return array
119
     */
120
    public function getControllerInfo(): array
121
    {
122
        return [
123
            'path' => "{$this->baseFolder}/{$this->paths['controllers']}",
124
            'namespace' => $this->namespaces['controllers'],
125
        ];
126
    }
127
128
    /**
129
     * @param string   $baseFolder
130
     * @param array    $paths
131
     * @param array    $namespaces
132
     * @param Request  $request
133
     * @param Response $response
134
     * @param array    $middlewares
135
     *
136
     * @return RouterCommand
137
     */
138
    public static function getInstance(
139
        string $baseFolder,
140
        array $paths,
141
        array $namespaces,
142
        Request $request,
143
        Response $response,
144
        array $middlewares
145
    ) {
146
        if (null === self::$instance) {
147
            self::$instance = new static(
148
                $baseFolder, $paths, $namespaces,
149
                $request, $response, $middlewares
150
            );
151
        }
152
153
        return self::$instance;
154
    }
155
156
    /**
157
     * Run Route Middlewares
158
     *
159
     * @param $command
160
     *
161
     * @return mixed|void
162
     * @throws
163
     */
164
    public function beforeAfter($command)
165
    {
166
        if (empty($command)) {
167
            return;
168
        }
169
170
        $info = $this->getMiddlewareInfo();
171
        if (is_array($command)) {
172
            foreach ($command as $value) {
173
                $this->beforeAfter($value);
174
            }
175
        } elseif (is_string($command)) {
176
            $middleware = explode(':', $command);
177
            $params = [];
178
            if (count($middleware) > 1) {
179
                $params = explode(',', $middleware[1]);
180
            }
181
182
            $resolvedMiddleware = $this->resolveMiddleware($middleware[0]);
183
            $response = false;
184
            if (is_array($resolvedMiddleware)) {
185
                foreach ($resolvedMiddleware as $middleware) {
186
                    $response = $this->runMiddleware(
187
                        $command,
188
                        $this->resolveMiddleware($middleware),
0 ignored issues
show
Bug introduced by
It seems like $this->resolveMiddleware($middleware) can also be of type array; however, parameter $middleware of Ballybran\Routing\Router...ommand::runMiddleware() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
                        /** @scrutinizer ignore-type */ $this->resolveMiddleware($middleware),
Loading history...
189
                        $params,
190
                        $info
191
                    );
192
                }
193
                return $response;
194
            }
195
196
            return $this->runMiddleware($command, $resolvedMiddleware, $params, $info);
197
        }
198
199
        return;
200
    }
201
202
    /**
203
     * Run Route Command; Controller or Closure
204
     *
205
     * @param $command
206
     * @param $params
207
     *
208
     * @return mixed|void
209
     * @throws
210
     */
211
    public function runRoute($command, $params = [])
212
    {
213
        $info = $this->getControllerInfo();
214
        if (!is_object($command)) {
215
            [$class, $method] = explode('@', $command);
216
            $class = str_replace([$info['namespace'], '\\', '.'], ['', '/', '/'], $class);
217
218
            $controller = $this->resolveClass($class, $info['path'], $info['namespace']);
219
            if (!method_exists($controller, $method)) {
220
                return $this->exception("{$method} method is not found in {$class} class.");
221
            }
222
223
            if (property_exists($controller, 'middlewareBefore') && is_array($controller->middlewareBefore)) {
224
                foreach ($controller->middlewareBefore as $middleware) {
225
                    $this->beforeAfter($middleware);
226
                }
227
            }
228
229
            $response = $this->runMethodWithParams([$controller, $method], $params);
230
231
            if (property_exists($controller, 'middlewareAfter') && is_array($controller->middlewareAfter)) {
232
                foreach ($controller->middlewareAfter as $middleware) {
233
                    $this->beforeAfter($middleware);
234
                }
235
            }
236
237
            return $response;
238
        }
239
240
        return $this->runMethodWithParams($command, $params);
241
    }
242
243
    /**
244
     * Resolve Controller or Middleware class.
245
     *
246
     * @param string $class
247
     * @param string $path
248
     * @param string $namespace
249
     *
250
     * @return object
251
     * @throws
252
     */
253
    protected function resolveClass(string $class, string $path, string $namespace)
254
    {
255
        $class = str_replace([$namespace, '\\'], ['', '/'], $class);
256
        $file = realpath("{$path}/{$class}.php");
257
        if (!file_exists($file)) {
258
            return $this->exception("{$class} class is not found. Please check the file.");
259
        }
260
261
        $class = $namespace . str_replace('/', '\\', $class);
262
        if (!class_exists($class)) {
263
            require_once($file);
264
        }
265
266
        return new $class();
267
    }
268
269
    /**
270
     * @param array|Closure $function
271
     * @param array         $params
272
     *
273
     * @return Response|mixed
274
     * @throws ReflectionException
275
     */
276
    protected function runMethodWithParams($function, array $params)
277
    {
278
        $reflection = is_array($function)
279
            ? new ReflectionMethod($function[0], $function[1])
280
            : new ReflectionFunction($function);
281
        $parameters = $this->resolveCallbackParameters($reflection, $params);
282
        $response = call_user_func_array($function, $parameters);
283
        return $this->sendResponse($response);
284
    }
285
286
    /**
287
     * @param Reflector $reflection
288
     * @param array     $uriParams
289
     *
290
     * @return array
291
     */
292
    protected function resolveCallbackParameters(Reflector $reflection, array $uriParams): array
293
    {
294
        $parameters = [];
295
        foreach ($reflection->getParameters() as $key => $param) {
0 ignored issues
show
Bug introduced by
The method getParameters() does not exist on Reflector. It seems like you code against a sub-type of Reflector such as ReflectionFunctionAbstract. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

295
        foreach ($reflection->/** @scrutinizer ignore-call */ getParameters() as $key => $param) {
Loading history...
296
            $class = $param->getClass();
297
            if (!is_null($class) && $class->isInstance($this->request)) {
298
                $parameters[] = $this->request;
299
            } elseif (!is_null($class) && $class->isInstance($this->response)) {
300
                $parameters[] = $this->response;
301
            } elseif (!is_null($class)) {
302
                $parameters[] = null;
303
            } else {
304
                if (empty($uriParams)) {
305
                    continue;
306
                }
307
                $uriParams = array_reverse($uriParams);
308
                $parameters[] = array_pop($uriParams);
309
                $uriParams = array_reverse($uriParams);
310
            }
311
        }
312
313
        return $parameters;
314
    }
315
316
    /**
317
     * @param $command
318
     * @param $middleware
319
     * @param $params
320
     * @param $info
321
     *
322
     * @return bool|RouterException
323
     * @throws ReflectionException
324
     */
325
    protected function runMiddleware(string $command, string $middleware, array $params, array $info)
326
    {
327
        $middlewareMethod = 'handle'; // For now, it's constant.
328
        $controller = $this->resolveClass($middleware, $info['path'], $info['namespace']);
329
330
        if (in_array($className = get_class($controller), $this->markedMiddlewares)) {
331
            return true;
332
        }
333
        array_push($this->markedMiddlewares, $className);
334
335
        if (!method_exists($controller, $middlewareMethod)) {
336
            return $this->exception("{$middlewareMethod}() method is not found in {$middleware} class.");
337
        }
338
339
        $parameters = $this->resolveCallbackParameters(new ReflectionMethod($controller, $middlewareMethod), $params);
340
        $response = call_user_func_array([$controller, $middlewareMethod], $parameters);
341
        if ($response !== true) {
342
            $this->sendResponse($response);
343
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
344
        }
345
346
        return $response;
347
    }
348
349
    /**
350
     * @param string $middleware
351
     *
352
     * @return array|string
353
     */
354
    protected function resolveMiddleware(string $middleware)
355
    {
356
        $middlewares = $this->middlewares;
357
        if (isset($middlewares['middlewareGroups'][$middleware])) {
358
            return $middlewares['middlewareGroups'][$middleware];
359
        }
360
361
        $name = explode(':', $middleware)[0];
362
        if (isset($middlewares['routeMiddlewares'][$name])) {
363
            return $middlewares['routeMiddlewares'][$name];
364
        }
365
366
        return $middleware;
367
    }
368
369
    /**
370
     * @param $response
371
     *
372
     * @return Response|mixed
373
     */
374
    protected function sendResponse($response)
375
    {
376
        if (is_array($response)) {
377
            $this->response->headers->set('Content-Type', 'application/json');
378
            return $this->response
379
                ->setContent(json_encode($response))
380
                ->send();
381
        }
382
383
        if (!is_string($response)) {
384
            return $response instanceof Response ? $response->send() : print($response);
385
        }
386
387
        return $this->response->setContent($response)->send();
388
    }
389
390
    /**
391
     * Throw new Exception for Router Error
392
     *
393
     * @param string $message
394
     * @param int    $statusCode
395
     *
396
     * @return RouterException
397
     * @throws Exception
398
     */
399
    protected function exception($message = '', $statusCode = 500)
400
    {
401
        return new RouterException($message, $statusCode);
402
    }
403
}
404