Passed
Push — master ( 53af6f...4b20bd )
by Alexander
02:25 queued 10s
created

SapiEmitter::shouldOutputBody()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 10.1359

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 6
nop 1
dl 0
loc 22
ccs 9
cts 15
cp 0.6
crap 10.1359
rs 8.8333
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 15
    public function __construct(?int $bufferSize = null)
30
    {
31 15
        if ($bufferSize !== null && $bufferSize <= 0) {
32
            throw new \InvalidArgumentException('Buffer size must be greater than zero');
33
        }
34 15
        $this->bufferSize = $bufferSize ?? self::DEFAULT_BUFFER_SIZE;
35 15
    }
36
37 15
    public function emit(ResponseInterface $response, bool $withoutBody = false): bool
38
    {
39 15
        $status = $response->getStatusCode();
40 15
        $withoutBody = $withoutBody || !$this->shouldOutputBody($response);
41 15
        $withoutContentLength = $withoutBody || $response->hasHeader('Transfer-Encoding');
42
43
        // we can't send headers if they are already sent
44 15
        if (headers_sent()) {
45 1
            throw new HeadersHaveBeenSentException();
46
        }
47 14
        header_remove();
48
        // send HTTP Status-Line
49 14
        header(sprintf(
50 14
            'HTTP/%s %d %s',
51 14
            $response->getProtocolVersion(),
52
            $status,
53 14
            $response->getReasonPhrase()
54 14
        ), true, $status);
55
        // filter headers
56 14
        $headers = $withoutContentLength
57 9
            ? $response->withoutHeader('Content-Length')
58 9
                       ->getHeaders()
59 14
            : $response->getHeaders();
60
        // send headers
61 14
        foreach ($headers as $header => $values) {
62 13
            $replaceFirst = strtolower($header) !== 'set-cookie';
63 13
            foreach ($values as $value) {
64 13
                header(sprintf('%s: %s', $header, $value), $replaceFirst);
65 13
                $replaceFirst = false;
66
            }
67
        }
68
69 14
        if (!$withoutBody) {
70 5
            if (!$withoutContentLength && !$response->hasHeader('Content-Length')) {
71 3
                $contentLength = $response->getBody()->getSize();
72 3
                if ($contentLength !== null) {
73 3
                    header('Content-Length: ' . $contentLength, true);
74
                }
75
            }
76
77 5
            $this->emitBody($response);
78
        }
79
80 14
        return true;
81
    }
82
83 5
    private function emitBody(ResponseInterface $response): void
84
    {
85 5
        $body = $response->getBody();
86 5
        if ($body->isSeekable()) {
87 5
            $body->rewind();
88
        }
89 5
        while (!$body->eof()) {
90 5
            echo $body->read($this->bufferSize);
91 5
            \flush();
92
        }
93 5
    }
94
95 14
    private function shouldOutputBody(ResponseInterface $response): bool
96
    {
97 14
        if (\in_array($response->getStatusCode(), self::NO_BODY_RESPONSE_CODES, true)) {
98 6
            return false;
99
        }
100
        // check if body is empty
101 8
        $body = $response->getBody();
102 8
        if (!$body->isReadable()) {
103 1
            return false;
104
        }
105 7
        $size = $body->getSize();
106 7
        if ($size !== null) {
107 7
            return $size > 0;
108
        }
109
        if ($body->isSeekable()) {
110
            $body->rewind();
111
            $byte = $body->read(1);
112
            if ($byte === '' || $body->eof()) {
113
                return false;
114
            }
115
        }
116
        return true;
117
    }
118
}
119