Completed
Pull Request — master (#188)
by Alexander
04:45
created

SapiEmitter::emitBody()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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