Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
Fallback
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 emit
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 discardBuffers
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Core\Emitter;
6
7use Psr\Http\Message\ResponseInterface as Response;
8use Throwable;
9
10/**
11 * Last resort for a request that failed outside the error handler.
12 *
13 * @internal
14 */
15final class Fallback
16{
17    /**
18     * Emits the response if nothing was sent yet. Output buffers the request
19     * opened are discarded first. If the emitter still fails, for example
20     * because the request wrote output into a buffer it did not open or one
21     * that cannot be cleaned, the status is set to 500 when possible and
22     * that output becomes the body.
23     */
24    public static function emit(
25        Emitter $emitter,
26        Response $response,
27        int $bufferLevel,
28        bool $withoutBody = false,
29    ): void {
30        self::discardBuffers($bufferLevel);
31
32        try {
33            $emitter->emit($response, $withoutBody);
34        } catch (Throwable) {
35            if (!headers_sent()) {
36                http_response_code($response->getStatusCode());
37            }
38        }
39    }
40
41    /**
42     * A buffer opened without the removable flag cannot be ended:
43     * ob_end_clean() fails and leaves it in place. The cleanup stops there,
44     * after discarding its output if allowed, and the response goes into
45     * that buffer, which PHP sends when the request ends.
46     */
47    private static function discardBuffers(int $bufferLevel): void
48    {
49        while (ob_get_level() > $bufferLevel) {
50            $flags = (int) (ob_get_status()['flags'] ?? 0);
51
52            if (($flags & PHP_OUTPUT_HANDLER_REMOVABLE) === 0) {
53                if (($flags & PHP_OUTPUT_HANDLER_CLEANABLE) !== 0) {
54                    ob_clean();
55                }
56
57                return;
58            }
59
60            ob_end_clean();
61        }
62    }
63}