SapiEmitter::emit()   B
last analyzed

Complexity

Conditions 11
Paths 104

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 11

Importance

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

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