Test Failed
Push — master ( 114cec...08f770 )
by Divine Niiquaye
14:45 queued 02:19
created

Application::getRouter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade;
19
20
use Biurad\Http\{Request, Response, Response\HtmlResponse};
21
use Biurad\Http\Factory\Psr17Factory;
22
use Biurad\Http\Interfaces\Psr17Interface;
23
use Flight\Routing\{Exceptions\RouteNotFoundException, Route, RouteCollection, Router};
24
use Fig\Http\Message\RequestMethodInterface;
25
use Flight\Routing\Generator\GeneratedUri;
26
use Flight\Routing\Interfaces\{RouteMatcherInterface, UrlGeneratorInterface};
27
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
28
use Psr\Http\Server\{MiddlewareInterface, RequestHandlerInterface};
29
use Laminas\Stratigility\Middleware\{CallableMiddlewareDecorator, RequestHandlerMiddleware};
30
use Laminas\{HttpHandlerRunner\Emitter\SapiStreamEmitter, Stratigility\Utils};
0 ignored issues
show
Bug introduced by
The type Laminas\HttpHandlerRunne...itter\SapiStreamEmitter 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
use Psr\EventDispatcher\EventDispatcherInterface;
32
use Rade\DI\Definitions\{Reference, Statement};
33
use Symfony\Component\Console\Application as ConsoleApplication;
34
use Symfony\Component\HttpFoundation\RequestStack;
35
36
/**
37
 * The Rade framework core class.
38
 *
39
 * @author Divine Niiquaye Ibok <[email protected]>
40
 */
41
class Application extends DI\Container implements RouterInterface, KernelInterface
42
{
43
    use Traits\HelperTrait;
44
45
    public const VERSION = '2.0.0-DEV';
46
47
    /**
48
     * Instantiate a new Application.
49 14
     */
50
    public function __construct(Psr17Interface $psr17Factory = null, EventDispatcherInterface $dispatcher = null, bool $debug = false)
51 14
    {
52
        parent::__construct();
53 14
        $this->parameters['debug'] ??= $debug;
54 14
55 14
        if (!isset($this->parameters['project.compiled_container_class'])) {
56 14
            $this->definitions = [
57 14
                'http.router' => $this->services['http.router'] = Router::withCollection(),
58 14
                'request_stack' => $this->services['request_stack'] = new RequestStack(),
59
                'psr17.factory' => $this->services['psr17.factory'] = $psr17Factory = $psr17Factory ?? new Psr17Factory(),
60 14
                'events.dispatcher' => $this->services['events.dispatcher'] = $dispatcher = $dispatcher ?? new Handler\EventHandler(),
61
            ];
62
            $this->types += [
63
                RequestStack::class => ['request_stack'],
64
                Router::class => ['http.router'],
65 14
                RouteMatcherInterface::class => ['http.router'],
66
                UrlGeneratorInterface::class => ['http.router'],
67
            ];
68 14
            $this->types(['psr17.factory' => DI\Resolver::autowireService($psr17Factory), 'events.dispatcher' => DI\Resolver::autowireService($dispatcher)]);
69 14
        }
70
    }
71
72
    /**
73
     * If true, exception will be thrown on resolvable services with are not typed.
74
     */
75
    public function strictAutowiring(bool $boolean = true): void
76
    {
77
        $this->resolver->setStrictAutowiring($boolean);
78
    }
79
80
    public function getDispatcher(): EventDispatcherInterface
81 4
    {
82
        return $this->services['events.dispatcher'] ?? $this->get('events.dispatcher');
83 4
    }
84
85
    public function getRouter(): Router
86
    {
87
        return $this->services['http.router'] ?? $this->get('http.router');
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     *
93
     * @param MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable ...$middlewares
94
     */
95
    public function pipe(object ...$middlewares): void
96
    {
97
        $this->getRouter()->pipe(...$this->resolveMiddlewares($middlewares));
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     *
103
     * @param MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable ...$middlewares
104
     */
105
    public function pipes(string $named, object ...$middlewares): void
106
    {
107
        $this->getRouter()->pipes($named, ...$this->resolveMiddlewares($middlewares));
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
114
    {
115
        return $this->getRouter()->generateUri($routeName, $parameters);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getRouter(...routeName, $parameters) returns the type Flight\Routing\Generator\GeneratedUri which is incompatible with the return type mandated by Rade\RouterInterface::generateUri() of Rade\GeneratedUri.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
116
    }
117 7
118
    /**
119 7
     * {@inheritdoc}
120
     */
121
    public function match(string $pattern, array $methods = Route::DEFAULT_METHODS, $to = null): Route
122
    {
123
        return $this->getRouter()->getCollection()->add(new Route($pattern, $methods, $to), false)->getRoute();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getRouter(...to), false)->getRoute() could return the type null which is incompatible with the type-hinted return Flight\Routing\Route. Consider adding an additional type-check to rule them out.
Loading history...
124
    }
125 1
126
    /**
127 1
     * {@inheritdoc}
128
     */
129
    public function post(string $pattern, $to = null): Route
130
    {
131
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
132
    }
133 1
134
    /**
135 1
     * {@inheritdoc}
136
     */
137
    public function put(string $pattern, $to = null): Route
138
    {
139
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
140
    }
141 1
142
    /**
143 1
     * {@inheritdoc}
144
     */
145
    public function delete(string $pattern, $to = null): Route
146
    {
147
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public function options(string $pattern, $to = null): Route
154
    {
155
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
156
    }
157 1
158
    /**
159 1
     * {@inheritdoc}
160
     */
161
    public function patch(string $pattern, $to = null): Route
162
    {
163
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
164
    }
165 1
166
    /**
167 1
     * {@inheritdoc}
168
     */
169
    public function group(string $prefix, $collection = null): RouteCollection
170
    {
171
        return $this->getRouter()->getCollection()->group($prefix, $collection);
172
    }
173
174
    /**
175
     * Handles the request and delivers the response.
176
     *
177
     * @param ServerRequestInterface|null $request Request to process
178
     *
179
     * @throws \Throwable
180
     *
181
     * @return int|bool
182
     */
183
    public function run(ServerRequestInterface $request = null, bool $catch = true)
184
    {
185
        if ($this->isRunningInConsole()) {
186
            $this->get(ConsoleApplication::class)->run();
187
        }
188
189
        if (null === $request) {
190
            $request = $this->get('psr17.factory')->fromGlobalRequest();
191
        }
192
193
        $response = $this->handle($request, $catch);
194
195
        if ($response instanceof Response) {
196
            $response->getResponse()->send();
197
198
            return true;
199
        }
200
201
        if (!\class_exists(SapiStreamEmitter::class)) {
202
            throw new \RuntimeException(\sprintf('Unable to emit response onto the browser. Try running "composer require laminas/laminas-httphandlerrunner".'));
203
        }
204
205
        return (new SapiStreamEmitter())->emit($response);
206
    }
207
208
    /**
209
     * Handles a request to convert it to a response.
210
     *
211 4
     * Exceptions are not caught.
212
     *
213 4
     * @param bool $catch Whether to catch exceptions or not
214 4
     */
215
    public function handle(ServerRequestInterface $request, bool $catch = true): ResponseInterface
216
    {
217
        if (!$this->has(RequestHandlerInterface::class)) {
218 4
            $this->definitions[RequestHandlerInterface::class] = $this->services[RequestHandlerInterface::class] = new Handler\RouteHandler($this);
219
        }
220 4
221 4
        try {
222
            $response = $this->getRouter()->process($request, $this->get(RequestHandlerInterface::class));
223
224 4
            if ($request instanceof Request) {
225
                $request = $request->withRequest($this->get('request_stack')->getMainRequest());
226
            }
227
228
            $this->getDispatcher()->dispatch($event = new Event\ResponseEvent($this, $request, $response));
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type Biurad\Http\Request; however, parameter $request of Rade\Event\ResponseEvent::__construct() does only seem to accept Psr\Http\Message\ServerRequestInterface, 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

228
            $this->getDispatcher()->dispatch($event = new Event\ResponseEvent($this, /** @scrutinizer ignore-type */ $request, $response));
Loading history...
229
        } catch (\Throwable $e) {
230
            if (!$catch || $this->isRunningInConsole()) {
231 4
                throw $e;
232 4
            }
233
234
            return $this->handleThrowable($e, $request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type Biurad\Http\Request; however, parameter $request of Rade\Application::handleThrowable() does only seem to accept Psr\Http\Message\ServerRequestInterface, 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

234
            return $this->handleThrowable($e, /** @scrutinizer ignore-type */ $request);
Loading history...
235 4
        } finally {
236
            $this->getDispatcher()->dispatch(new Event\TerminateEvent($this, $request));
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type Biurad\Http\Request; however, parameter $request of Rade\Event\TerminateEvent::__construct() does only seem to accept Psr\Http\Message\ServerRequestInterface, 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

236
            $this->getDispatcher()->dispatch(new Event\TerminateEvent($this, /** @scrutinizer ignore-type */ $request));
Loading history...
237
        }
238
239
        return $event->getResponse();
240
    }
241
242
    /**
243
     * Handle RouteNotFoundException for Flight Routing.
244
     *
245
     * @return RouteNotFoundException|ResponseInterface
246
     */
247
    protected function handleRouterException(RouteNotFoundException $e, ServerRequestInterface $request)
248
    {
249
        if (empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
250
            $pathInfo = $request->getUri()->getPath();
251
        }
252
253
        if ('/' === $pathInfo) {
254
            return $this->createWelcomeResponse();
255
        }
256
257
        $message = $e->getMessage();
258
259
        if ('' !== $referer = $request->getHeaderLine('referer')) {
260
            $message .= \sprintf(' (from "%s")', $referer);
261
        }
262
263
        return new RouteNotFoundException($message, 404);
264
    }
265
266
    /**
267
     * Handles a throwable by trying to convert it to a Response.
268
     *
269
     * @throws \Throwable
270
     */
271
    protected function handleThrowable(\Throwable $e, ServerRequestInterface $request): ResponseInterface
272
    {
273
        if ($request instanceof Request) {
274
            $this->get('request_stack')->push($request->getRequest());
275
        }
276
277
        $this->getDispatcher()->dispatch($event = new Event\ExceptionEvent($this, $request, $e));
278
279
        // a listener might have replaced the exception
280
        $e = $event->getThrowable();
281
282
        if (null === $response = $event->getResponse()) {
283
            if ($e instanceof RouteNotFoundException) {
284
                $e = $this->handleRouterException($e, $request);
285
286
                if ($e instanceof ResponseInterface) {
287
                    return $e;
288
                }
289
            }
290
291
            throw $e;
292
        }
293
294
        // ensure that we actually have an error response and keep the HTTP status code and headers
295
        if (!$event->isAllowingCustomResponseCode()) {
296
            $response = $response->withStatus(Utils::getStatusCode($e, $response));
297
        }
298
299
        return $response;
300
    }
301
302
    /**
303
     * The default welcome page for application.
304
     */
305
    protected function createWelcomeResponse(): ResponseInterface
306
    {
307
        $debug = $this->parameters['debug'];
308
        $version = self::VERSION;
309
        $docVersion = $version[0] . '.x.x';
310
311
        \ob_start();
312
313
        include __DIR__ . '/Resources/welcome.phtml';
314
315
        return new HtmlResponse((string) \ob_get_clean(), 404);
316
    }
317
318
    /**
319
     * Resolve Middlewares.
320
     *
321
     * @param array<int,MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable> $middlewares
322
     *
323
     * @return array<int,MiddlewareInterface>
324
     */
325
    protected function resolveMiddlewares(array $middlewares): array
326
    {
327
        foreach ($middlewares as $offset => $middleware) {
328
            if ($middleware instanceof RequestHandlerInterface) {
329
                $middlewares[$offset] = new RequestHandlerMiddleware($middleware);
330
            } elseif ($middleware instanceof Statement) {
331
                $middlewares[$offset] = $this->resolver->resolve($middleware->getValue(), $middleware->getArguments());
332
            } elseif ($middleware instanceof Reference) {
333
                $middlewares[$offset] = $this->get((string) $middleware);
334
            } elseif (\is_callable($middleware)) {
335
                $middlewares[$offset] = new CallableMiddlewareDecorator($middleware);
0 ignored issues
show
Bug introduced by
It seems like $middleware can also be of type Psr\Http\Server\MiddlewareInterface; however, parameter $middleware of Laminas\Stratigility\Mid...ecorator::__construct() does only seem to accept callable, 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

335
                $middlewares[$offset] = new CallableMiddlewareDecorator(/** @scrutinizer ignore-type */ $middleware);
Loading history...
336
            }
337
        }
338
339
        return $middlewares;
340
    }
341
}
342