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
|
|||
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 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
integer
values, zero is a special case, in particular the following results might be unexpected: