Passed
Push — master ( c2ea24...6d36db )
by Alexander
01:47
created

SapiEmitter::shouldOutputBody()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 5
nop 1
dl 0
loc 19
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Yiisoft\Http\Status;
9
use Yiisoft\Yii\Web\Exception\HeadersHaveBeenSentException;
10
11
/**
12
 * SapiEmitter sends a response using PHP Server API
13
 */
14
final class SapiEmitter
15
{
16
    private const NO_BODY_RESPONSE_CODES = [
17
        Status::CONTINUE,
18
        Status::SWITCHING_PROTOCOLS,
19
        Status::PROCESSING,
20
        Status::NO_CONTENT,
21
        Status::RESET_CONTENT,
22
        Status::NOT_MODIFIED
23
    ];
24
25
    private const DEFAULT_BUFFER_SIZE = 8_388_608; // 8MB
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Web\8_388_608 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
26
27
    private int $bufferSize;
28
29
    public function __construct(?int $bufferSize = null)
30
    {
31
        if ($bufferSize !== null && $bufferSize <= 0) {
32
            throw new \InvalidArgumentException('Buffer size must be greater than zero');
33
        }
34
        $this->bufferSize = $bufferSize ?? self::DEFAULT_BUFFER_SIZE;
35
    }
36
37
    public function emit(ResponseInterface $response, bool $withoutBody = false): bool
38
    {
39
        $status = $response->getStatusCode();
40
        $withoutBody = $withoutBody || !$this->shouldOutputBody($response);
41
        $withoutContentLength = $withoutBody || $response->hasHeader('Transfer-Encoding');
42
43
        // we can't send headers if they are already sent
44
        if (headers_sent()) {
45
            throw new HeadersHaveBeenSentException();
46
        }
47
        header_remove();
48
        // send HTTP Status-Line
49
        header(sprintf(
50
            'HTTP/%s %d %s',
51
            $response->getProtocolVersion(),
52
            $status,
53
            $response->getReasonPhrase()
54
        ), true, $status);
55
        // filter headers
56
        $headers = $withoutContentLength
57
            ? $response->withoutHeader('Content-Length')
58
                       ->getHeaders()
59
            : $response->getHeaders();
60
        // send headers
61
        foreach ($headers as $header => $values) {
62
            $replaceFirst = strtolower($header) !== 'set-cookie';
63
            foreach ($values as $value) {
64
                header(sprintf('%s: %s', $header, $value), $replaceFirst);
65
                $replaceFirst = false;
66
            }
67
        }
68
69
        if (!$withoutBody) {
70
            if (!$withoutContentLength && !$response->hasHeader('Content-Length')) {
71
                $contentLength = $response->getBody()->getSize();
72
                if ($contentLength !== null) {
73
                    header('Content-Length: ' . $contentLength, true);
74
                }
75
            }
76
77
            $this->emitBody($response);
78
        }
79
80
        return true;
81
    }
82
83
    private function emitBody(ResponseInterface $response): void
84
    {
85
        $body = $response->getBody();
86
        if ($body->isSeekable()) {
87
            $body->rewind();
88
        }
89
        while (!$body->eof()) {
90
            echo $body->read($this->bufferSize);
91
            \flush();
92
        }
93
    }
94
95
    private function shouldOutputBody(ResponseInterface $response): bool
96
    {
97
        if (\in_array($response->getStatusCode(), self::NO_BODY_RESPONSE_CODES, true)) {
98
            return false;
99
        }
100
        // check if body is empty
101
        $body = $response->getBody();
102
        $size = $body->getSize();
103
        if ($size !== null) {
104
            return $size > 0;
105
        }
106
        if ($body->isSeekable()) {
107
            $body->rewind();
108
            $byte = $body->read(1);
109
            if ($byte === '' || $body->eof()) {
110
                return false;
111
            }
112
        }
113
        return true;
114
    }
115
}
116