Passed
Pull Request — master (#347)
by
unknown
02:40
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
    /**
38
     * @param ResponseInterface $response
39
     * @param bool $withoutBody
40
     *
41
     * @throws HeadersHaveBeenSentException
42
     *
43
     * @return bool
44
     */
45 15
    public function emit(ResponseInterface $response, bool $withoutBody = false): bool
46
    {
47 15
        $status = $response->getStatusCode();
48 15
        $withoutBody = $withoutBody || !$this->shouldOutputBody($response);
49 15
        $withoutContentLength = $withoutBody || $response->hasHeader('Transfer-Encoding');
50 15
        if ($withoutContentLength) {
51 9
            $response = $response->withoutHeader('Content-Length');
52
        }
53
54
        // we can't send headers if they are already sent
55 15
        if (headers_sent()) {
56 1
            throw new HeadersHaveBeenSentException();
57
        }
58 14
        header_remove();
59
        // send HTTP Status-Line
60 14
        header(\sprintf(
61 14
            'HTTP/%s %d %s',
62 14
            $response->getProtocolVersion(),
63 14
            $status,
64 14
            $response->getReasonPhrase()
65 14
        ), true, $status);
66
        // send headers
67 14
        foreach ($response->getHeaders() as $header => $values) {
68 13
            $replaceFirst = \strtolower($header) !== 'set-cookie';
69 13
            foreach ($values as $value) {
70 13
                header("{$header}: {$value}", $replaceFirst);
71 13
                $replaceFirst = false;
72
            }
73
        }
74
75 14
        if (!$withoutBody) {
76 5
            if (!$withoutContentLength && !$response->hasHeader('Content-Length')) {
77 3
                $contentLength = $response->getBody()->getSize();
78 3
                if ($contentLength !== null) {
79 3
                    header("Content-Length: {$contentLength}", true);
80
                }
81
            }
82
83 5
            $this->emitBody($response);
84
        }
85
86 14
        return true;
87
    }
88
89 5
    private function emitBody(ResponseInterface $response): void
90
    {
91 5
        $body = $response->getBody();
92 5
        if ($body->isSeekable()) {
93 5
            $body->rewind();
94
        }
95 5
        while (!$body->eof()) {
96 5
            echo $body->read($this->bufferSize);
97 5
            \flush();
98
        }
99 5
    }
100
101 14
    private function shouldOutputBody(ResponseInterface $response): bool
102
    {
103 14
        if (\in_array($response->getStatusCode(), self::NO_BODY_RESPONSE_CODES, true)) {
104 6
            return false;
105
        }
106
        // check if body is empty
107 8
        $body = $response->getBody();
108 8
        if (!$body->isReadable()) {
109 1
            return false;
110
        }
111 7
        $size = $body->getSize();
112 7
        if ($size !== null) {
113 7
            return $size > 0;
114
        }
115
        if ($body->isSeekable()) {
116
            $body->rewind();
117
            $byte = $body->read(1);
118
            if ($byte === '' || $body->eof()) {
119
                return false;
120
            }
121
        }
122
        return true;
123
    }
124
}
125