Test Setup Failed
Push — develop ( ed9389...5203c7 )
by Henry
02:49
created

Emitter::emitHeaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 12
rs 10
1
<?php
2
/**
3
 * This file is part of the Divergence package.
4
 *
5
 * (c) Henry Paradiz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
declare(strict_types=1);
11
12
namespace Divergence\Responders;
13
14
use Psr\Http\Message\ResponseInterface;
15
use function connection_status;
16
use function header;
17
use function headers_sent;
18
use function in_array;
19 50
use function min;
20
use function sprintf;
21 50
use function strlen;
22
23
use function strtolower;
24
25
use const CONNECTION_NORMAL;
26
27
/**
28
 * Forked from Slim Framework 4.x
29 50
 *
30
 */
31 50
class Emitter
32
{
33
    private int $responseChunkSize;
34
35
    protected ResponseInterface $response;
36
37
    public function __construct(ResponseInterface $response)
38
    {
39
        $this->response = $response;
40
    }
41
42 50
    public function setResponseChunkSize($chunkSize): void
43 50
    {
44 50
        $this->responseChunkSize = $chunkSize;
45
    }
46
47 50
    /**
48 50
     * Send the response the client
49 50
     */
50
    public function emit(): void
51
    {
52 50
        if (!isset($this->responseChunkSize)) {
53 50
            $this->responseChunkSize= 4096;
54 50
        }
55 50
        $response = $this->response;
56 50
        $isEmpty = $this->isResponseEmpty($response);
57
        if (headers_sent() === false) {
58 50
            $this->emitHeaders($response);
59
60 50
            // Set the status _after_ the headers, because of PHP's "helpful" behavior with location headers.
61
            // See https://github.com/slimphp/Slim/issues/1730
62
63
            $this->emitStatusLine($response);
64
        }
65
66
        if (!$isEmpty) {
67
            $this->emitBody($response);
68
        }
69
    }
70
71
    /**
72
     * Emit Response Headers
73
     */
74
    private function emitHeaders(ResponseInterface $response): void
75
    {
76
        foreach ($response->getHeaders() as $name => $values) {
77
            $first = strtolower($name) !== 'set-cookie';
78
            foreach ($values as $value) {
79
                $header = sprintf('%s: %s', $name, $value);
80
                header($header, $first);
81
                $first = false;
82
            }
83
        }
84
    }
85
86
    /**
87
     * Emit Status Line
88
     */
89
    private function emitStatusLine(ResponseInterface $response): void
90
    {
91
        $statusLine = sprintf(
92
            'HTTP/%s %s %s',
93
            $response->getProtocolVersion(),
94
            $response->getStatusCode(),
95
            $response->getReasonPhrase()
96
        );
97
        header($statusLine, true, $response->getStatusCode());
98
    }
99
100
    /**
101
     * Emit Body
102
     */
103
    private function emitBody(ResponseInterface $response): void
104
    {
105
        $body = $response->getBody();
106
107
        // partial content responses might have a pre-set stream seek
108
        if ($body->isSeekable() && $response->getStatusCode() !== 206) {
109
            $body->rewind();
110
        }
111
112
        $amountToRead = (int) $response->getHeaderLine('Content-Length');
113
        if (!$amountToRead) {
114
            $amountToRead = $body->getSize();
115
        }
116
117
        if ($amountToRead) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $amountToRead of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
118
            while ($amountToRead > 0 && !$body->eof()) {
119
                $length = min($this->responseChunkSize, $amountToRead);
120
                $data = $body->read($length);
121
                echo $data;
122
123
                $amountToRead -= strlen($data);
124
125
                if (connection_status() !== CONNECTION_NORMAL) {
126
                    break;
127
                }
128
            }
129
        } else {
130
            while (!$body->eof()) {
131
                echo $body->read($this->responseChunkSize);
132
                if (connection_status() !== CONNECTION_NORMAL) {
133
                    break;
134
                }
135
            }
136
        }
137
    }
138
139
    /**
140
     * Asserts response body is empty or status code is 204, 205 or 304
141
     */
142
    public function isResponseEmpty(ResponseInterface $response): bool
143
    {
144
        if (in_array($response->getStatusCode(), [204, 205, 304], true)) {
145
            return true;
146
        }
147
        $stream = $response->getBody();
148
        $seekable = $stream->isSeekable();
149
        if ($seekable) {
150
            $stream->rewind();
151
        }
152
        return $seekable ? $stream->read(1) === '' : $stream->eof();
153
    }
154
}
155