Application::delete()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2019 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Flange;
14
15
use Biurad\Http\Factory\Psr17Factory;
16
use Biurad\Http\Interfaces\Psr17Interface;
17
use Biurad\Http\{Request, Response, Response\HtmlResponse};
18
use Fig\Http\Message\RequestMethodInterface;
19
use Flight\Routing\Interfaces\{RouteMatcherInterface, UrlGeneratorInterface};
20
use Flight\Routing\{Exceptions\RouteNotFoundException, RouteCollection, RouteUri, Router};
21
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...
22
use Psr\EventDispatcher\EventDispatcherInterface;
23
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
24
use Psr\Http\Server\{MiddlewareInterface, RequestHandlerInterface};
25
use Rade\DI;
26
use Symfony\Component\Console\Application as ConsoleApplication;
27
use Symfony\Component\HttpFoundation\RequestStack;
28
29
use function Rade\DI\Loader\service;
30
31
/**
32
 * The Rade framework core class.
33
 *
34
 * @author Divine Niiquaye Ibok <[email protected]>
35
 */
36
class Application extends DI\Container implements RouterInterface, KernelInterface
37
{
38
    use Traits\HelperTrait;
39
40
    public const VERSION = '2.0.0-DEV';
41
42
    /**
43
     * Instantiate a new Application.
44
     */
45 14
    public function __construct(Psr17Interface $psr17Factory = null, EventDispatcherInterface $dispatcher = null, bool $debug = false)
46
    {
47 14
        parent::__construct();
48 14
        $this->parameters['debug'] ??= $debug;
49
50 14
        if (!isset($this->types[Router::class])) {
51 14
            $this->definitions = [
52 14
                'request_stack' => service($this->services['request_stack'] = new RequestStack())->typed(RequestStack::class)->setContainer($this, 'request_stack'),
53 14
                'http.router' => service($this->services['http.router'] = new Router())->typed(Router::class, RouteMatcherInterface::class, UrlGeneratorInterface::class)->setContainer($this, 'http.router'),
54 14
                'psr17.factory' => service($this->services['psr17.factory'] = $psr17Factory ?? new Psr17Factory())->typed()->setContainer($this, 'psr17.factory'),
55 14
                RequestHandlerInterface::class => service(Handler\RouteHandler::class)->setContainer($this, RequestHandlerInterface::class),
56
            ];
57
58 14
            if (null !== $dispatcher) {
59
                $this->autowire('events.dispatcher', $this->services['events.dispatcher'] = $dispatcher);
60
            }
61
        }
62
    }
63
64
    /**
65
     * If true, exception will be thrown on resolvable services with are not typed.
66
     */
67
    public function strictAutowiring(bool $boolean = true): void
68
    {
69
        $this->getResolver()->setStrictAutowiring($boolean);
70
    }
71
72 4
    public function getDispatcher(): ?EventDispatcherInterface
73
    {
74 4
        return $this->get('events.dispatcher', DI\Container::NULL_ON_INVALID_SERVICE);
75
    }
76
77 7
    public function getRouter(): Router
78
    {
79 7
        return $this->get('http.router');
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     *
85
     * @param MiddlewareInterface ...$middlewares
86
     */
87
    public function pipe(object ...$middlewares): void
88
    {
89
        $this->getRouter()->pipe(...$middlewares);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     *
95
     * @param MiddlewareInterface ...$middlewares
96
     */
97
    public function pipes(string $named, object ...$middlewares): void
98
    {
99
        $this->getRouter()->pipes($named, ...$middlewares);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function generateUri(string $routeName, array $parameters = []): RouteUri
106
    {
107
        return $this->getRouter()->generateUri($routeName, $parameters);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 7
    public function match(string $pattern, array $methods = ['GET'], mixed $to = null)
114
    {
115 7
        return $this->getRouter()->getCollection()->add($pattern, $methods, $to);
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 1
    public function post(string $pattern, mixed $to = null)
122
    {
123 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129 1
    public function put(string $pattern, mixed $to = null)
130
    {
131 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 1
    public function delete(string $pattern, mixed $to = null)
138
    {
139 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function options(string $pattern, mixed $to = null)
146
    {
147
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 1
    public function patch(string $pattern, mixed $to = null)
154
    {
155 1
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 1
    public function group(string $prefix, callable|RouteCollection|null $collection = null): RouteCollection
162
    {
163 1
        return $this->getRouter()->getCollection()->group($prefix, $collection);
164
    }
165
166
    /**
167
     * Handles the request and delivers the response.
168
     *
169
     * @return int Exit status 0 on success, any other number on failure (e.g. 1)
170
     *
171
     * @throws \Throwable
172
     */
173
    public function run(ServerRequestInterface $request = null, bool $catch = true): int
174
    {
175
        if (!$this->isRunningInConsole()) {
176
            $response = $this->handle($request ?? $this->get('psr17.factory')->fromGlobalRequest(), $catch);
177
178
            if ($response instanceof Response) {
179
                $code = $response->getResponse()->send()->getStatusCode();
180
181
                return $code >= 200 && $code < 400 ? 0 : 1;
182
            }
183
184
            if (!\class_exists(SapiStreamEmitter::class)) {
185
                throw new \RuntimeException('You must install the laminas/laminas-httphandlerrunner package to emit a response.');
186
            }
187
188
            return (new SapiStreamEmitter())->emit($response) ? 0 : 1;
189
        }
190
191
        return $this->get(ConsoleApplication::class)->run();
192
    }
193
194
    /**
195
     * Handles a request to convert it to a response.
196
     *
197
     * Exceptions are not caught.
198
     *
199
     * @param bool $catch Whether to catch exceptions or not
200
     */
201 4
    public function handle(ServerRequestInterface $request, bool $catch = true): ResponseInterface
202
    {
203
        try {
204 4
            $event = $this->getDispatcher()?->dispatch(new Event\RequestEvent($this, $request));
205
206 4
            if (null !== $event) {
207
                $request = $event->getRequest();
208
            }
209
210 4
            if ($request instanceof Request) {
211 4
                $this->get('request_stack')->push($request->getRequest());
212
            }
213
214 4
            $response = $event?->getResponse() ?? $this->getRouter()->process($request, $this->get(RequestHandlerInterface::class));
215
216 4
            if ($request instanceof Request) {
217 4
                $request = $request->withRequest($this->get('request_stack')->getCurrentRequest());
218
            }
219
220 4
            $event = $this->getDispatcher()?->dispatch(new Event\ResponseEvent($this, $request, $response));
221
        } catch (\Throwable $e) {
222
            if (!$catch || $this->isRunningInConsole()) {
223
                throw $e;
224
            }
225
226
            return $this->handleThrowable($e, $request);
227
        } finally {
228 4
            $this->getDispatcher()?->dispatch(new Event\TerminateEvent($this, $request));
229
        }
230
231 4
        return $event?->getResponse() ?? $response;
232
    }
233
234
    /**
235
     * Handle RouteNotFoundException for Flight Routing.
236
     *
237
     * @return RouteNotFoundException|ResponseInterface
238
     */
239
    protected function handleRouterException(RouteNotFoundException $e, ServerRequestInterface $request)
240
    {
241
        if (empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
242
            $pathInfo = $request->getUri()->getPath();
243
        }
244
245
        if ('/' === $pathInfo) {
246
            return $this->createWelcomeResponse();
247
        }
248
249
        $message = $e->getMessage();
250
251
        if ('' !== $referer = $request->getHeaderLine('referer')) {
252
            $message .= \sprintf(' (from "%s")', $referer);
253
        }
254
255
        return new RouteNotFoundException($message, 404);
256
    }
257
258
    /**
259
     * Handles a throwable by trying to convert it to a Response.
260
     *
261
     * @throws \Throwable
262
     */
263
    protected function handleThrowable(\Throwable $e, ServerRequestInterface $request): ResponseInterface
264
    {
265
        if ($request instanceof Request) {
266
            $this->get('request_stack')->push($request->getRequest());
267
        }
268
269
        $event = $this->getDispatcher()?->dispatch(new Event\ExceptionEvent($this, $request, $e));
270
271
        // a listener might have replaced the exception
272
        if (null !== $event) {
273
            $e = $event->getThrowable();
274
        }
275
276
        if (null === $response = $event?->getResponse()) {
277
            if ($e instanceof RouteNotFoundException) {
278
                $e = $this->handleRouterException($e, $request);
279
280
                if ($e instanceof ResponseInterface) {
281
                    return $e;
282
                }
283
            }
284
285
            throw $e;
286
        }
287
288
        // ensure that we actually have an error response and keep the HTTP status code and headers
289
        if (!$event->isAllowingCustomResponseCode()) {
290
            $response = $response->withStatus(Utils::getStatusCode($e, $response));
291
        }
292
293
        return $response;
294
    }
295
296
    /**
297
     * The default welcome page for application.
298
     */
299
    protected function createWelcomeResponse(): ResponseInterface
300
    {
301
        $debug = $this->parameters['debug'];
302
        $version = self::VERSION;
303
        $docVersion = $version[0].'.x.x';
304
305
        \ob_start();
306
307
        include __DIR__.'/Resources/welcome.phtml';
308
309
        return new HtmlResponse((string) \ob_get_clean(), 404);
310
    }
311
}
312