Emitter   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Test Coverage

Coverage 56.89%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 53
dl 0
loc 125
ccs 33
cts 58
cp 0.5689
rs 10
c 1
b 0
f 0
wmc 25

7 Methods

Rating   Name   Duplication   Size   Complexity  
A emit() 0 18 4
A emitStatusLine() 0 9 1
A __construct() 0 3 1
A emitHeaders() 0 8 3
A setResponseChunkSize() 0 3 1
B emitBody() 0 31 10
A isResponseEmpty() 0 14 5
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
use function min;
20
use function sprintf;
21
use function strlen;
22
23
use function strtolower;
24
25
use const CONNECTION_NORMAL;
26
27
/**
28
 * Forked from Slim Framework 4.x
29
 *
30
 */
31
class Emitter
32
{
33
    private int $responseChunkSize;
34
35
    protected ResponseInterface $response;
36
37 62
    public function __construct(ResponseInterface $response)
38
    {
39 62
        $this->response = $response;
40
    }
41
42
    public function setResponseChunkSize($chunkSize): void
43
    {
44
        $this->responseChunkSize = $chunkSize;
45
    }
46
47
    /**
48
     * Send the response the client
49
     */
50 62
    public function emit(): void
51
    {
52 62
        if (!isset($this->responseChunkSize)) {
53 62
            $this->responseChunkSize= 4096;
54
        }
55 62
        $response = $this->response;
56 62
        $isEmpty = $this->isResponseEmpty($response);
57 62
        if (headers_sent() === false) {
58
            $this->emitHeaders($response);
59
60
            // 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 62
        if (!$isEmpty) {
67 62
            $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 62
    private function emitBody(ResponseInterface $response): void
104
    {
105 62
        $body = $response->getBody();
106
107
        // partial content responses might have a pre-set stream seek
108 62
        if ($body->isSeekable() && $response->getStatusCode() !== 206) {
109 59
            $body->rewind();
110
        }
111
112 62
        $amountToRead = (int) $response->getHeaderLine('Content-Length');
113 62
        if (!$amountToRead) {
114 52
            $amountToRead = $body->getSize();
115
        }
116
117 62
        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 62
            while ($amountToRead > 0 && !$body->eof()) {
119 62
                $length = min($this->responseChunkSize, $amountToRead);
120 62
                $data = $body->read($length);
121 62
                echo $data;
122
123 62
                $amountToRead -= strlen($data);
124
125 62
                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 62
    public function isResponseEmpty(ResponseInterface $response): bool
143
    {
144 62
        if (in_array($response->getStatusCode(), [204, 205, 304], true)) {
145
            return true;
146
        }
147 62
        if (in_array($response->getStatusCode(), [206], true)) {
148 3
            return false;
149
        }
150 59
        $stream = $response->getBody();
151 59
        $seekable = $stream->isSeekable();
152 59
        if ($seekable) {
153 59
            $stream->rewind();
154
        }
155 59
        return $seekable ? $stream->read(1) === '' : $stream->eof();
156
    }
157
}
158