Passed
Push — master ( 7656d0...dbe1c1 )
by Divine Niiquaye
04:04
created

Application   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Test Coverage

Coverage 34.95%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 92
c 7
b 0
f 0
dl 0
loc 296
ccs 36
cts 103
cp 0.3495
rs 8.96
wmc 43

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 3
A match() 0 3 1
A pipe() 0 3 1
A getDispatcher() 0 3 1
A delete() 0 3 1
A handleRouterException() 0 17 4
A generateUri() 0 3 1
A handleThrowable() 0 29 6
A post() 0 3 1
A resolveMiddlewares() 0 15 6
A put() 0 3 1
A pipes() 0 3 1
A handle() 0 25 6
A group() 0 3 1
A createWelcomeResponse() 0 11 1
A strictAutowiring() 0 3 1
A run() 0 23 5
A options() 0 3 1
A patch() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Application often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Application, and based on these observations, apply Extract Interface, too.

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\{Interfaces\Psr17Interface, Request, Response, Response\HtmlResponse};
21
use Biurad\Http\Factory\Psr17Factory;
22
use Flight\Routing\{Exceptions\RouteNotFoundException, Route, RouteCollection, Router};
23
use Fig\Http\Message\RequestMethodInterface;
24
use Flight\Routing\Generator\GeneratedUri;
25
use Flight\Routing\Interfaces\RouteMatcherInterface;
26
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
27
use Psr\Http\Server\{MiddlewareInterface, RequestHandlerInterface};
28
use Laminas\Stratigility\Middleware\{CallableMiddlewareDecorator, RequestHandlerMiddleware};
29
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...
30
use Psr\EventDispatcher\EventDispatcherInterface;
31
use Rade\DI\Definitions\{Reference, Statement};
32
use Symfony\Component\Console\Application as ConsoleApplication;
33
use Symfony\Component\HttpFoundation\RequestStack;
34
35
/**
36
 * The Rade framework core class.
37
 *
38
 * @author Divine Niiquaye Ibok <[email protected]>
39
 */
40
class Application extends DI\Container implements RouterInterface, KernelInterface
41
{
42
    use Traits\HelperTrait;
43
44
    public const VERSION = '2.0.0-DEV';
45
46
    /**
47
     * Instantiate a new Application.
48
     */
49 14
    public function __construct(Psr17Interface $psr17Factory = null, EventDispatcherInterface $dispatcher = null, bool $debug = false)
50
    {
51 14
        parent::__construct();
52
53 14
        if (!isset($this->parameters['project.compiled_container_class'])) {
54 14
            $this->definitions = [
55 14
                'http.router' => Router::withCollection(),
56 14
                'request_stack' => new RequestStack(),
57 14
                'psr17.factory' => $psr17Factory = $psr17Factory ?? new Psr17Factory(),
58 14
                'events.dispatcher' => $dispatcher = $dispatcher ?? new Handler\EventHandler(),
59
            ];
60 14
            $this->types += [
61
                RequestStack::class => ['request_stack'],
62
                Router::class => ['http.router'],
63
                RouteMatcherInterface::class => ['http.router'],
64
            ];
65 14
            $this->types(['psr17.factory' => DI\Resolver::autowireService($psr17Factory), 'events.dispatcher' => DI\Resolver::autowireService($dispatcher)]);
66
        }
67
68 14
        if (!isset($this->parameters['debug'])) {
69 14
            $this->parameters['debug'] = $debug;
70
        }
71
    }
72
73
    /**
74
     * If true, exception will be thrown on resolvable services with are not typed.
75
     */
76
    public function strictAutowiring(bool $boolean = true): void
77
    {
78
        $this->resolver->setStrictAutowiring($boolean);
79
    }
80
81 4
    public function getDispatcher(): EventDispatcherInterface
82
    {
83 4
        return $this->services['events.dispatcher'] ?? $this->get('events.dispatcher');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->services['...et('events.dispatcher') could return the type array|mixed which is incompatible with the type-hinted return Psr\EventDispatcher\EventDispatcherInterface. Consider adding an additional type-check to rule them out.
Loading history...
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     *
89
     * @param MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable ...$middlewares
90
     */
91
    public function pipe(object ...$middlewares): void
92
    {
93
        $this->get('http.router')->pipe(...$this->resolveMiddlewares($middlewares));
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * @param MiddlewareInterface|RequestHandlerInterface|Reference|Statement|callable ...$middlewares
100
     */
101
    public function pipes(string $named, object ...$middlewares): void
102
    {
103
        $this->get('http.router')->pipes($named, ...$this->resolveMiddlewares($middlewares));
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
110
    {
111
        return $this->get('http.router')->generateUri($routeName, $parameters);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 7
    public function match(string $pattern, array $methods = Route::DEFAULT_METHODS, $to = null): Route
118
    {
119 7
        return ($this->services['http.router'] ?? $this->get('http.router'))->getCollection()->addRoute($pattern, $methods, $to)->getRoute();
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125 1
    public function post(string $pattern, $to = null): Route
126
    {
127 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 1
    public function put(string $pattern, $to = null): Route
134
    {
135 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141 1
    public function delete(string $pattern, $to = null): Route
142
    {
143 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function options(string $pattern, $to = null): Route
150
    {
151
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 1
    public function patch(string $pattern, $to = null): Route
158
    {
159 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165 1
    public function group(string $prefix, $collection = null): RouteCollection
166
    {
167 1
        return $this->get('http.router')->getCollection()->group($prefix, $collection);
168
    }
169
170
    /**
171
     * Handles the request and delivers the response.
172
     *
173
     * @param ServerRequestInterface|null $request Request to process
174
     *
175
     * @throws \Throwable
176
     *
177
     * @return int|bool
178
     */
179
    public function run(ServerRequestInterface $request = null, bool $catch = true)
180
    {
181
        if ($this->isRunningInConsole()) {
182
            $this->get(ConsoleApplication::class)->run();
183
        }
184
185
        if (null === $request) {
186
            $request = $this->get('psr17.factory')->fromGlobalRequest();
187
        }
188
189
        $response = $this->handle($request, $catch);
190
191
        if ($response instanceof Response) {
192
            $response->getResponse()->send();
193
194
            return true;
195
        }
196
197
        if (class_exists(SapiStreamEmitter::class)) {
198
            return (new SapiStreamEmitter())->emit($response);
199
        }
200
201
        throw new \RuntimeException(\sprintf('Unable to emit response onto the browser. Try running "composer require laminas/laminas-httphandlerrunner".'));
202
    }
203
204
    /**
205
     * Handles a request to convert it to a response.
206
     *
207
     * Exceptions are not caught.
208
     *
209
     * @param bool $catch Whether to catch exceptions or not
210
     */
211 4
    public function handle(ServerRequestInterface $request, bool $catch = true): ResponseInterface
212
    {
213 4
        if (!$this->has(RequestHandlerInterface::class)) {
214 4
            $this->definitions[RequestHandlerInterface::class] = new Handler\RouteHandler($this);
215
        }
216
217
        try {
218 4
            $response = $this->get('http.router')->process($request, $this->get(RequestHandlerInterface::class));
219
220 4
            if ($request instanceof Request) {
221 4
                $request = $request->withRequest($this->get('request_stack')->getMainRequest());
222
            }
223
224 4
            $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

224
            $this->getDispatcher()->dispatch($event = new Event\ResponseEvent($this, /** @scrutinizer ignore-type */ $request, $response));
Loading history...
225
        } catch (\Throwable $e) {
226
            if (!$catch || $this->isRunningInConsole()) {
227
                throw $e;
228
            }
229
230
            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

230
            return $this->handleThrowable($e, /** @scrutinizer ignore-type */ $request);
Loading history...
231 4
        } finally {
232 4
            $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

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

331
                $middlewares[$offset] = new CallableMiddlewareDecorator(/** @scrutinizer ignore-type */ $middleware);
Loading history...
332
            }
333
        }
334
335
        return $middlewares;
336
    }
337
}
338