Test Failed
Push — master ( a25a4c...498f08 )
by Divine Niiquaye
13:17
created

Application::mount()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 16
c 2
b 0
f 0
nc 6
nop 2
dl 0
loc 28
rs 8.4444
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\{Factory\NyholmPsr7Factory, Interfaces\Psr17Interface, Response\HtmlResponse};
21
use Flight\Routing\{Exceptions\RouteNotFoundException, Route, RouteCollection, Router};
22
use Flight\Routing\Generator\GeneratedUri;
23
use Flight\Routing\Interfaces\RouteMatcherInterface;
24
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
25
use Psr\Http\Server\{MiddlewareInterface, RequestHandlerInterface};
26
use Laminas\Stratigility\Middleware\{CallableMiddlewareDecorator, RequestHandlerMiddleware};
27
use Laminas\{HttpHandlerRunner\Emitter\SapiStreamEmitter, Stratigility\Utils};
0 ignored issues
show
Coding Style introduced by
Compound namespaces cannot have a depth more than 2
Loading history...
28
use Psr\EventDispatcher\EventDispatcherInterface;
29
use Rade\DI\Definitions\{Reference, Statement};
30
use Symfony\Component\Console\Application as ConsoleApplication;
31
32
/**
33
 * The Rade framework core class.
34
 *
35
 * @author Divine Niiquaye Ibok <[email protected]>
36
 */
37
class Application extends DI\Container implements RouterInterface
38
{
39
    use Traits\HelperTrait;
40
41
    public const VERSION = '2.0.0-DEV';
42
43
    /**
44
     * Instantiate a new Application.
45
     */
46
    public function __construct(Psr17Interface $psr17Factory = null, EventDispatcherInterface $dispatcher = null, bool $debug = false)
47
    {
48
        parent::__construct();
49
50
        if (empty($this->methodsMap)) {
51
            $this->definitions['http.router'] = Router::withCollection();
52
            $this->definitions['psr17.factory'] = $psr17Factory = ($psr17Factory ?? new NyholmPsr7Factory());
53
            $this->definitions['events.dispatcher'] = $dispatcher = ($dispatcher ?? new Handler\EventHandler());
54
55
            $this->types(
56
                [
57
                    'http.router' => [Router::class, RouteMatcherInterface::class],
58
                    'psr17.factory' => DI\Resolvers\Resolver::autowireService($psr17Factory),
59
                    'events.dispatcher' => DI\Resolvers\Resolver::autowireService($dispatcher),
60
                ]
61
            );
62
        }
63
64
        $this->parameters['debug'] ??= $debug;
65
    }
66
67
    public function strictAutowiring(bool $boolean = true): void
68
    {
69
        $this->resolver->setStrictAutowiring($boolean);
70
    }
71
72
    public function getDispatcher(): EventDispatcherInterface
73
    {
74
        return $this->services['events.dispatcher'] ?? $this->get('events.dispatcher');
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     *
80
     * @param MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable ...$middlewares
81
     */
82
    public function pipe(object ...$middlewares): void
83
    {
84
        $this->get('http.router')->pipe(...$this->resolveMiddlewares($middlewares));
0 ignored issues
show
Bug introduced by
The method pipe() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

84
        $this->get('http.router')->/** @scrutinizer ignore-call */ pipe(...$this->resolveMiddlewares($middlewares));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
introduced by
The method pipe() does not exist on Rade\DI\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

84
        $this->get('http.router')->/** @scrutinizer ignore-call */ pipe(...$this->resolveMiddlewares($middlewares));
Loading history...
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     *
90
     * @param MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable ...$middlewares
91
     */
92
    public function pipes(string $named, object ...$middlewares): void
93
    {
94
        $this->get('http.router')->pipes($named, ...$this->resolveMiddlewares($middlewares));
0 ignored issues
show
Bug introduced by
The method pipes() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

94
        $this->get('http.router')->/** @scrutinizer ignore-call */ pipes($named, ...$this->resolveMiddlewares($middlewares));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
introduced by
The method pipes() does not exist on Rade\DI\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

94
        $this->get('http.router')->/** @scrutinizer ignore-call */ pipes($named, ...$this->resolveMiddlewares($middlewares));
Loading history...
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
101
    {
102
        return $this->get('http.router')->generateUri($routeName, $parameters);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('http....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...
Bug introduced by
The method generateUri() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

102
        return $this->get('http.router')->/** @scrutinizer ignore-call */ generateUri($routeName, $parameters);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
introduced by
The method generateUri() does not exist on Rade\DI\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

102
        return $this->get('http.router')->/** @scrutinizer ignore-call */ generateUri($routeName, $parameters);
Loading history...
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function match(string $pattern, array $methods = Route::DEFAULT_METHODS, $to = null): Route
109
    {
110
        return ($this->services['http.router'] ?? $this->get('http.router'))->getCollection()->addRoute($pattern, $methods, $to)->getRoute();
0 ignored issues
show
Bug introduced by
The method getCollection() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

110
        return ($this->services['http.router'] ?? $this->get('http.router'))->/** @scrutinizer ignore-call */ getCollection()->addRoute($pattern, $methods, $to)->getRoute();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getCollection() does not exist on Rade\Application. ( Ignorable by Annotation )

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

110
        return ($this->services['http.router'] ?? $this->get('http.router'))->/** @scrutinizer ignore-call */ getCollection()->addRoute($pattern, $methods, $to)->getRoute();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getCollection() does not exist on Rade\DI\Container. ( Ignorable by Annotation )

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

110
        return ($this->services['http.router'] ?? $this->get('http.router'))->/** @scrutinizer ignore-call */ getCollection()->addRoute($pattern, $methods, $to)->getRoute();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function post(string $pattern, $to = null): Route
117
    {
118
        return $this->match($pattern, [Router::METHOD_POST], $to);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function put(string $pattern, $to = null): Route
125
    {
126
        return $this->match($pattern, [Router::METHOD_PUT], $to);
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function delete(string $pattern, $to = null): Route
133
    {
134
        return $this->match($pattern, [Router::METHOD_DELETE], $to);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function options(string $pattern, $to = null): Route
141
    {
142
        return $this->match($pattern, [Router::METHOD_OPTIONS], $to);
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function patch(string $pattern, $to = null): Route
149
    {
150
        return $this->match($pattern, [Router::METHOD_PATCH], $to);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function group(string $prefix, $collection = null): RouteCollection
157
    {
158
        return $this->get('http.router')->getCollection()->group($prefix, $collection);
159
    }
160
161
    /**
162
     * Handles the request and delivers the response.
163
     *
164
     * @param ServerRequestInterface|null $request Request to process
165
     *
166
     * @return int|bool
167
     */
168
    public function run(ServerRequestInterface $request = null, bool $catch = true)
169
    {
170
        if ($this->isRunningInConsole()) {
171
            $this->get(ConsoleApplication::class)->run();
0 ignored issues
show
introduced by
The method run() does not exist on Rade\DI\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

171
            $this->get(ConsoleApplication::class)->/** @scrutinizer ignore-call */ run();
Loading history...
Bug introduced by
The method run() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

171
            $this->get(ConsoleApplication::class)->/** @scrutinizer ignore-call */ run();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
172
        }
173
174
        if (null === $request) {
175
            $request = $this->get('psr17.factory')->fromGlobalRequest();
0 ignored issues
show
Bug introduced by
The method fromGlobalRequest() does not exist on Rade\DI\Container. ( Ignorable by Annotation )

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

175
            $request = $this->get('psr17.factory')->/** @scrutinizer ignore-call */ fromGlobalRequest();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method fromGlobalRequest() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

175
            $request = $this->get('psr17.factory')->/** @scrutinizer ignore-call */ fromGlobalRequest();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method fromGlobalRequest() does not exist on Rade\Application. ( Ignorable by Annotation )

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

175
            $request = $this->get('psr17.factory')->/** @scrutinizer ignore-call */ fromGlobalRequest();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
176
        }
177
178
        return (new SapiStreamEmitter())->emit($this->handle($request, $catch));
179
    }
180
181
    /**
182
     * Handles a request to convert it to a response.
183
     *
184
     * Exceptions are not caught.
185
     *
186
     * @param bool $catch Whether to catch exceptions or not
187
     */
188
    public function handle(ServerRequestInterface $request, bool $catch = true): ResponseInterface
189
    {
190
        if (!$this->has(RequestHandlerInterface::class)) {
191
            $this->definitions[RequestHandlerInterface::class] = new Handler\RouteHandler($this);
192
        }
193
194
        try {
195
            $this->getDispatcher()->dispatch($event = new Event\RequestEvent($this, $request));
196
197
            if ($event->hasResponse()) {
198
                return $event->getResponse();
199
            }
200
201
            $request = $event->getRequest();
202
            $response = $this->get('http.router')->process($request, $this->get(RequestHandlerInterface::class));
0 ignored issues
show
Bug introduced by
The method process() does not exist on Rade\Application. ( Ignorable by Annotation )

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

202
            $response = $this->get('http.router')->/** @scrutinizer ignore-call */ process($request, $this->get(RequestHandlerInterface::class));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method process() does not exist on Rade\DI\Container. ( Ignorable by Annotation )

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

202
            $response = $this->get('http.router')->/** @scrutinizer ignore-call */ process($request, $this->get(RequestHandlerInterface::class));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method process() does not exist on PhpParser\Builder\Method. ( Ignorable by Annotation )

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

202
            $response = $this->get('http.router')->/** @scrutinizer ignore-call */ process($request, $this->get(RequestHandlerInterface::class));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
203
204
            $this->getDispatcher()->dispatch($event = new Event\ResponseEvent($this, $request, $response));
205
        } catch (\Throwable $e) {
206
            if (!$catch || $this->isRunningInConsole()) {
207
                throw $e;
208
            }
209
210
            return $this->handleThrowable($e, $request);
211
        } finally {
212
            $this->getDispatcher()->dispatch(new Event\TerminateEvent($this, $request));
213
        }
214
215
        return $event->getResponse();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $event->getResponse() could return the type null which is incompatible with the type-hinted return Psr\Http\Message\ResponseInterface. Consider adding an additional type-check to rule them out.
Loading history...
216
    }
217
218
    /**
219
     * Handle RouteNotFoundException for Flight Routing.
220
     *
221
     * @return RouteNotFoundException|ResponseInterface
222
     */
223
    protected function handleRouterException(RouteNotFoundException $e, ServerRequestInterface $request)
224
    {
225
        if (empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
226
            $pathInfo = $request->getUri()->getPath();
227
        }
228
229
        if ('/' === $pathInfo) {
230
            return $this->createWelcomeResponse();
231
        }
232
233
        $message = $e->getMessage();
234
235
        if ('' !== $referer = $request->getHeaderLine('referer')) {
236
            $message .= \sprintf(' (from "%s")', $referer);
237
        }
238
239
        return new RouteNotFoundException($message, 404);
240
    }
241
242
    /**
243
     * Handles a throwable by trying to convert it to a Response.
244
     *
245
     * @throws \Throwable
246
     */
247
    protected function handleThrowable(\Throwable $e, ServerRequestInterface $request): ResponseInterface
248
    {
249
        $this->getDispatcher()->dispatch($event = new Event\ExceptionEvent($this, $request, $e));
250
251
        // a listener might have replaced the exception
252
        $e = $event->getThrowable();
253
254
        if (null === $response = $event->getResponse()) {
255
            if ($e instanceof RouteNotFoundException) {
256
                $e = $this->handleRouterException($e, $request);
257
258
                if ($e instanceof ResponseInterface) {
259
                    return $e;
260
                }
261
            }
262
263
            throw $e;
264
        }
265
266
        // ensure that we actually have an error response and keep the HTTP status code and headers
267
        if (!$event->isAllowingCustomResponseCode()) {
268
            $response = $response->withStatus(Utils::getStatusCode($e, $response));
269
        }
270
271
        return $response;
272
    }
273
274
    /**
275
     * The default welcome page for application.
276
     */
277
    protected function createWelcomeResponse(): ResponseInterface
278
    {
279
        $debug = $this->parameters['debug'];
280
        $version = self::VERSION;
281
        $docVersion = $version[0] . '.x.x';
282
283
        \ob_start();
284
285
        include __DIR__ . '/Resources/welcome.phtml';
286
287
        return new HtmlResponse((string) \ob_get_clean(), 404);
288
    }
289
290
    /**
291
     * Resolve Middlewares.
292
     *
293
     * @param array<int,MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable> $middlewares
294
     *
295
     * @return array<int,MiddlewareInterface>
296
     */
297
    protected function resolveMiddlewares(array $middlewares): array
298
    {
299
        foreach ($middlewares as $offset => $middleware) {
300
            if ($middleware instanceof RequestHandlerInterface) {
301
                $middlewares[$offset] = new RequestHandlerMiddleware($middleware);
302
            } elseif ($middleware instanceof Statement) {
303
                $middlewares[$offset] = $this->resolver->resolve($middleware->getValue(), $middleware->getArguments());
304
            } elseif ($middleware instanceof Reference) {
305
                $middlewares[$offset] = $this->get((string) $middleware);
306
            } elseif (\is_callable($middleware)) {
307
                $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

307
                $middlewares[$offset] = new CallableMiddlewareDecorator(/** @scrutinizer ignore-type */ $middleware);
Loading history...
308
            }
309
        }
310
311
        return $middlewares;
312
    }
313
}
314