Passed
Push — master ( 5c9806...4102cb )
by Alexander
04:15 queued 01:10
created

SapiEmitter::emit()   B

Complexity

Conditions 11
Paths 104

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 11

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 11
eloc 26
c 1
b 0
f 1
nc 104
nop 2
dl 0
loc 48
ccs 27
cts 27
cp 1
crap 11
rs 7.2833

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\Http;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\ResponseInterface;
9
use Yiisoft\Http\Status;
10
use Yiisoft\Yii\Runner\Http\Exception\HeadersHaveBeenSentException;
11
12
use function flush;
13
use function in_array;
14
use function sprintf;
15
use function strtolower;
16
17
/**
18
 * SapiEmitter sends a response using standard PHP Server API i.e. with {@see header()} and "echo".
19
 */
20
final class SapiEmitter
21
{
22
    private const NO_BODY_RESPONSE_CODES = [
23
        Status::CONTINUE,
24
        Status::SWITCHING_PROTOCOLS,
25
        Status::PROCESSING,
26
        Status::NO_CONTENT,
27
        Status::RESET_CONTENT,
28
        Status::NOT_MODIFIED,
29
    ];
30
31
    private const DEFAULT_BUFFER_SIZE = 8_388_608; // 8MB
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Runner\Http\8_388_608 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
32
33
    private int $bufferSize;
34
35 22
    public function __construct(int $bufferSize = null)
36
    {
37 22
        if ($bufferSize !== null && $bufferSize < 1) {
38 1
            throw new InvalidArgumentException('Buffer size must be greater than zero.');
39
        }
40
41 21
        $this->bufferSize = $bufferSize ?? self::DEFAULT_BUFFER_SIZE;
42 21
    }
43
44
    /**
45
     * Responds to the client with headers and body.
46
     *
47
     * @param ResponseInterface $response Response object to send.
48
     * @param bool $withoutBody If body should be ignored.
49
     *
50
     * @throws HeadersHaveBeenSentException
51
     */
52 21
    public function emit(ResponseInterface $response, bool $withoutBody = false): void
53
    {
54 21
        $status = $response->getStatusCode();
55 21
        $withoutBody = $withoutBody || !$this->shouldOutputBody($response);
56 21
        $withoutContentLength = $withoutBody || $response->hasHeader('Transfer-Encoding');
57
58 21
        if ($withoutContentLength) {
59 11
            $response = $response->withoutHeader('Content-Length');
60
        }
61
62
        // We can't send headers if they are already sent.
63 21
        if (headers_sent()) {
64 1
            throw new HeadersHaveBeenSentException();
65
        }
66 20
        header_remove();
67
68
        // Send headers.
69 20
        foreach ($response->getHeaders() as $header => $values) {
70
            /** @var string $header */
71 16
            $replaceFirst = strtolower($header) !== 'set-cookie';
72
73 16
            foreach ($values as $value) {
74 16
                header("$header: $value", $replaceFirst);
75 16
                $replaceFirst = false;
76
            }
77
        }
78
79
        // Send HTTP Status-Line (must be sent after the headers).
80 20
        header(sprintf(
81 20
            'HTTP/%s %d %s',
82 20
            $response->getProtocolVersion(),
83 20
            $status,
84 20
            $response->getReasonPhrase(),
85 20
        ), true, $status);
86
87 20
        if ($withoutBody) {
88 11
            return;
89
        }
90
91 9
        if (!$withoutContentLength && !$response->hasHeader('Content-Length')) {
92 7
            $contentLength = $response->getBody()->getSize();
93
94 7
            if ($contentLength !== null) {
95 6
                header("Content-Length: $contentLength", true);
96
            }
97
        }
98
99 9
        $this->emitBody($response);
100 9
    }
101
102 9
    private function emitBody(ResponseInterface $response): void
103
    {
104 9
        $body = $response->getBody();
105
106 9
        if ($body->isSeekable()) {
107 8
            $body->rewind();
108
        }
109
110 9
        while (!$body->eof()) {
111 8
            echo $body->read($this->bufferSize);
112 8
            flush();
113
        }
114 9
    }
115
116 20
    private function shouldOutputBody(ResponseInterface $response): bool
117
    {
118 20
        if (in_array($response->getStatusCode(), self::NO_BODY_RESPONSE_CODES, true)) {
119 6
            return false;
120
        }
121
122
        // Check if body is empty.
123 14
        $body = $response->getBody();
124
125 14
        if (!$body->isReadable()) {
126 1
            return false;
127
        }
128
129 13
        $size = $body->getSize();
130
131 13
        if ($size !== null) {
132 11
            return $size > 0;
133
        }
134
135 2
        if ($body->isSeekable()) {
136 1
            $body->rewind();
137 1
            $byte = $body->read(1);
138
139 1
            if ($byte === '' || $body->eof()) {
140 1
                return false;
141
            }
142
        }
143
144 1
        return true;
145
    }
146
}
147