RequestHandler::closeOutputBuffersWithClean()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Server\Handler;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override 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...
17
use Throwable;
18
use Valkyrja\Container\Manager\Container;
19
use Valkyrja\Container\Manager\Contract\ContainerContract;
20
use Valkyrja\Http\Message\Enum\StatusCode;
21
use Valkyrja\Http\Message\Request\Contract\ServerRequestContract;
22
use Valkyrja\Http\Message\Response\Contract\ResponseContract;
23
use Valkyrja\Http\Message\Response\Response as HttpResponse;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Http\Message\Response\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...
24
use Valkyrja\Http\Message\Stream\Stream;
25
use Valkyrja\Http\Message\Throwable\Exception\HttpException;
26
use Valkyrja\Http\Middleware\Handler\Contract\RequestReceivedHandlerContract;
27
use Valkyrja\Http\Middleware\Handler\Contract\SendingResponseHandlerContract;
28
use Valkyrja\Http\Middleware\Handler\Contract\TerminatedHandlerContract;
29
use Valkyrja\Http\Middleware\Handler\Contract\ThrowableCaughtHandlerContract;
30
use Valkyrja\Http\Middleware\Handler\RequestReceivedHandler;
31
use Valkyrja\Http\Middleware\Handler\SendingResponseHandler;
32
use Valkyrja\Http\Middleware\Handler\TerminatedHandler;
33
use Valkyrja\Http\Middleware\Handler\ThrowableCaughtHandler;
34
use Valkyrja\Http\Routing\Dispatcher\Contract\RouterContract;
35
use Valkyrja\Http\Routing\Dispatcher\Router;
36
use Valkyrja\Http\Server\Handler\Contract\RequestHandlerContract as Contract;
37
38
use function count;
39
use function defined;
40
use function fastcgi_finish_request;
41
use function function_exists;
42
use function in_array;
43
use function litespeed_finish_request;
0 ignored issues
show
introduced by
The function litespeed_finish_request was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
44
use function ob_end_clean;
45
use function ob_end_flush;
46
use function ob_get_status;
47
use function session_id;
48
use function session_write_close;
49
50
use const PHP_OUTPUT_HANDLER_CLEANABLE;
51
use const PHP_OUTPUT_HANDLER_FLUSHABLE;
52
use const PHP_OUTPUT_HANDLER_REMOVABLE;
53
use const PHP_SAPI;
54
55
class RequestHandler implements Contract
56
{
57
    public function __construct(
58
        protected ContainerContract $container = new Container(),
59
        protected RouterContract $router = new Router(),
60
        protected RequestReceivedHandlerContract $requestReceivedHandler = new RequestReceivedHandler(),
61
        protected ThrowableCaughtHandlerContract $throwableCaughtHandler = new ThrowableCaughtHandler(),
62
        protected SendingResponseHandlerContract $sendingResponseHandler = new SendingResponseHandler(),
63
        protected TerminatedHandlerContract $terminatedHandler = new TerminatedHandler(),
64
        protected bool $debug = false
65
    ) {
66
    }
67
68
    /**
69
     * @inheritDoc
70
     *
71
     * @throws Throwable
72
     */
73
    #[Override]
74
    public function handle(ServerRequestContract $request): ResponseContract
75
    {
76
        try {
77
            $response = $this->dispatchRouter($request);
78
        } catch (Throwable $throwable) {
79
            $response = $this->getResponseFromThrowable($throwable);
80
            $response = $this->throwableCaughtHandler->throwableCaught($request, $response, $throwable);
81
        }
82
83
        // Set the returned response in the container
84
        $this->container->setSingleton(ResponseContract::class, $response);
85
86
        return $response;
87
    }
88
89
    /**
90
     * @inheritDoc
91
     */
92
    #[Override]
93
    public function send(ResponseContract $response): static
94
    {
95
        $response->send();
96
97
        $this->finishSession();
98
        $this->finishRequest();
99
100
        return $this;
101
    }
102
103
    /**
104
     * @inheritDoc
105
     */
106
    #[Override]
107
    public function terminate(ServerRequestContract $request, ResponseContract $response): void
108
    {
109
        // Dispatch the terminable middleware
110
        $this->terminatedHandler->terminated($request, $response);
111
    }
112
113
    /**
114
     * @inheritDoc
115
     *
116
     * @throws Throwable
117
     */
118
    #[Override]
119
    public function run(ServerRequestContract $request): void
120
    {
121
        // Handle the request, dispatch the after request middleware
122
        $response = $this->handle($request);
123
        // Dispatch the sending middleware
124
        $response = $this->sendingResponseHandler->sendingResponse($request, $response);
125
126
        // Set the returned response in the container
127
        $this->container->setSingleton(ResponseContract::class, $response);
128
129
        // Send the response
130
        $this->send($response);
131
        // Terminate the application
132
        $this->terminate($request, $response);
133
    }
134
135
    /**
136
     * Dispatch the request via the router.
137
     *
138
     * @param ServerRequestContract $request The request
139
     */
140
    protected function dispatchRouter(ServerRequestContract $request): ResponseContract
141
    {
142
        // Set the request object in the container
143
        $this->container->setSingleton(ServerRequestContract::class, $request);
144
145
        // Dispatch the before request handled middleware
146
        $requestAfterMiddleware = $this->requestReceivedHandler->requestReceived($request);
147
148
        // If the return value after middleware is a response return it
149
        if ($requestAfterMiddleware instanceof ResponseContract) {
150
            return $requestAfterMiddleware;
151
        }
152
153
        // Set the returned request in the container
154
        $this->container->setSingleton(ServerRequestContract::class, $requestAfterMiddleware);
155
156
        return $this->router->dispatch($requestAfterMiddleware);
157
    }
158
159
    /**
160
     * Get a response from a throwable.
161
     *
162
     * @param Throwable $throwable The exception
163
     *
164
     * @throws Throwable
165
     */
166
    protected function getResponseFromThrowable(Throwable $throwable): ResponseContract
167
    {
168
        if ($this->debug) {
169
            throw $throwable;
170
        }
171
172
        // If no response has been set and there is a template with the error code
173
        if ($throwable instanceof HttpException) {
174
            return $throwable->getResponse()
175
                ?? $this->getDefaultErrorResponse($throwable);
176
        }
177
178
        return $this->getDefaultErrorResponse();
179
    }
180
181
    /**
182
     * Get the default exception response.
183
     *
184
     * @param HttpException|null $httpException [optional] The Http exception
185
     */
186
    protected function getDefaultErrorResponse(HttpException|null $httpException = null): ResponseContract
187
    {
188
        $statusCode = StatusCode::INTERNAL_SERVER_ERROR;
189
190
        $body = new Stream();
191
        $body->write('Unknown Server Error Occurred');
192
        $body->rewind();
193
194
        if ($httpException !== null) {
195
            $statusCode = $httpException->getStatusCode();
196
            $body->write('Unknown Server Error Occurred - ' . $httpException->getTraceCode());
197
            $body->rewind();
198
        }
199
200
        return new HttpResponse(
201
            body: $body,
202
            statusCode: $statusCode
203
        );
204
    }
205
206
    /**
207
     * Finish a session if it is active.
208
     */
209
    protected function finishSession(): void
210
    {
211
        if ($this->shouldCloseSession()) {
212
            $this->closeSession();
213
        }
214
    }
215
216
    /**
217
     * Determine if the session should be closed.
218
     */
219
    protected function shouldCloseSession(): bool
220
    {
221
        $sessionId = session_id();
222
223
        return $sessionId !== false && $sessionId !== '';
224
    }
225
226
    /**
227
     * Close the session.
228
     */
229
    protected function closeSession(): void
230
    {
231
        session_write_close();
232
    }
233
234
    /**
235
     * Finish the request.
236
     */
237
    protected function finishRequest(): void
238
    {
239
        // If fastcgi is enabled
240
        if ($this->shouldUseFastcgiToFinishRequest()) {
241
            // Use it to finish the request
242
            $this->finishRequestWithFastcgi();
243
        } elseif ($this->shouldUseLitespeedToFinishRequest()) {
244
            // If litespeed is enabled
245
            // Use it to finish the request
246
            $this->finishRequestWithLitespeed();
247
        } elseif ($this->shouldCloseOutputBuffersToFinishRequest()) {
248
            // Otherwise if this isn't a cli request
249
            // Use an internal method to finish the request
250
            $this->closeOutputBuffers(0, true);
251
        } else {
252
            $this->finishRequestForAllOtherTypes();
253
        }
254
    }
255
256
    /**
257
     * Determine if the request should be finished with Fastcgi.
258
     */
259
    protected function shouldUseFastcgiToFinishRequest(): bool
260
    {
261
        return function_exists('fastcgi_finish_request');
262
    }
263
264
    /**
265
     * Determine if the request should be finished with Litespeed.
266
     */
267
    protected function shouldUseLitespeedToFinishRequest(): bool
268
    {
269
        return function_exists('litespeed_finish_request');
270
    }
271
272
    /**
273
     * Determine if the request should be finished via closing the output buffers.
274
     */
275
    protected function shouldCloseOutputBuffersToFinishRequest(): bool
276
    {
277
        return ! in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true);
278
    }
279
280
    /**
281
     * Finish the request with Fastcgi.
282
     */
283
    protected function finishRequestWithFastcgi(): void
284
    {
285
        fastcgi_finish_request();
286
    }
287
288
    /**
289
     * Finish the request with Litespeed.
290
     */
291
    protected function finishRequestWithLitespeed(): void
292
    {
293
        /** @psalm-suppress UndefinedFunction */
294
        litespeed_finish_request();
0 ignored issues
show
Bug introduced by
The function litespeed_finish_request was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

294
        /** @scrutinizer ignore-call */ 
295
        litespeed_finish_request();
Loading history...
295
    }
296
297
    /**
298
     * Cleans or flushes output buffers up to target level.
299
     * Resulting level can be greater than target level if a non-removable
300
     * buffer has been encountered.
301
     *
302
     * @param int  $targetLevel The target output buffering level
303
     * @param bool $flush       Whether to flush or clean the buffers
304
     */
305
    protected function closeOutputBuffers(int $targetLevel, bool $flush): void
306
    {
307
        $status = $this->outputBuffersGetStatus();
308
        $level  = count($status);
309
310
        $flushOrCleanFlag = $flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE;
311
        // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
312
        $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | $flushOrCleanFlag : -1;
313
314
        while (
315
            $level-- > $targetLevel
316
            && ($s = $status[$level])
317
            && ($s['del'] ?? (! isset($s['flags']) || $flags === ($s['flags'] & $flags)))
318
        ) {
319
            if ($flush) {
320
                $this->closeOutputBuffersWithFlush();
321
            } else {
322
                $this->closeOutputBuffersWithClean();
323
            }
324
        }
325
    }
326
327
    /**
328
     * Get the status of the output buffers.
329
     *
330
     * @return array<int, ?array{chunk_size: int, buffer_size: int, buffer_used: int, flags?: int, level?: int, type?: int, del?: int, name?: string}>
331
     *
332
     * @psalm-suppress MixedReturnTypeCoercion
333
     */
334
    protected function outputBuffersGetStatus(): array
335
    {
336
        return ob_get_status(true);
337
    }
338
339
    /**
340
     * End the output buffers with flush.
341
     */
342
    protected function closeOutputBuffersWithFlush(): void
343
    {
344
        ob_end_flush();
345
    }
346
347
    /**
348
     * End the output buffers with clean.
349
     */
350
    protected function closeOutputBuffersWithClean(): void
351
    {
352
        ob_end_clean();
353
    }
354
355
    /**
356
     * Finish the request for any scenario not previously caught.
357
     */
358
    protected function finishRequestForAllOtherTypes(): void
359
    {
360
    }
361
}
362