1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | /** |
||||
6 | * Micro |
||||
7 | * |
||||
8 | * @author Raffael Sahli <[email protected]> |
||||
9 | * @copyright Copryright (c) 2015-2018 gyselroth GmbH (https://gyselroth.com) |
||||
10 | * @license MIT https://opensource.org/licenses/MIT |
||||
11 | */ |
||||
12 | |||||
13 | namespace Micro\Http; |
||||
14 | |||||
15 | use Closure; |
||||
16 | use SimpleXMLElement; |
||||
17 | |||||
18 | class Response |
||||
19 | { |
||||
20 | /** |
||||
21 | * Possible output formats. |
||||
22 | */ |
||||
23 | const OUTPUT_FORMATS = [ |
||||
24 | 'json' => 'application/json; charset=utf-8', |
||||
25 | 'xml' => 'application/xml; charset=utf-8', |
||||
26 | 'text' => 'text/html; charset=utf-8', |
||||
27 | ]; |
||||
28 | /** |
||||
29 | * Output format. |
||||
30 | * |
||||
31 | * @var string |
||||
32 | */ |
||||
33 | protected $output_format = 'json'; |
||||
34 | |||||
35 | /** |
||||
36 | * Human readable output. |
||||
37 | * |
||||
38 | * @var bool |
||||
39 | */ |
||||
40 | protected $pretty_format = false; |
||||
41 | |||||
42 | /** |
||||
43 | * Headers. |
||||
44 | * |
||||
45 | * @var array |
||||
46 | */ |
||||
47 | protected $headers = []; |
||||
48 | |||||
49 | /** |
||||
50 | * Code. |
||||
51 | * |
||||
52 | * @var int |
||||
53 | */ |
||||
54 | protected $code = 200; |
||||
55 | |||||
56 | /** |
||||
57 | * Body. |
||||
58 | * |
||||
59 | * @var string |
||||
60 | */ |
||||
61 | protected $body; |
||||
62 | |||||
63 | /** |
||||
64 | * Init response. |
||||
65 | */ |
||||
66 | public function __construct() |
||||
67 | { |
||||
68 | $this->setupFormats(); |
||||
69 | } |
||||
70 | |||||
71 | /** |
||||
72 | * Set header. |
||||
73 | * |
||||
74 | * @param string $header |
||||
75 | * |
||||
76 | * @return Response |
||||
77 | */ |
||||
78 | public function setHeader(string $header, string $value): self |
||||
79 | { |
||||
80 | $this->headers[$header] = $value; |
||||
81 | |||||
82 | return $this; |
||||
83 | } |
||||
84 | |||||
85 | /** |
||||
86 | * Delete header. |
||||
87 | * |
||||
88 | * @param string $header |
||||
89 | * |
||||
90 | * @return Response |
||||
91 | */ |
||||
92 | public function removeHeader(string $header): self |
||||
93 | { |
||||
94 | if (isset($this->headers[$header])) { |
||||
95 | unset($this->headers[$header]); |
||||
96 | } |
||||
97 | |||||
98 | return $this; |
||||
99 | } |
||||
100 | |||||
101 | /** |
||||
102 | * Get headers. |
||||
103 | * |
||||
104 | * @return array |
||||
105 | */ |
||||
106 | public function getHeaders(): array |
||||
107 | { |
||||
108 | return $this->headers; |
||||
109 | } |
||||
110 | |||||
111 | /** |
||||
112 | * Send headers. |
||||
113 | * |
||||
114 | * @return Response |
||||
115 | */ |
||||
116 | public function sendHeaders(): self |
||||
117 | { |
||||
118 | foreach ($this->headers as $header => $value) { |
||||
119 | header($header.': '.$value); |
||||
120 | } |
||||
121 | |||||
122 | return $this; |
||||
123 | } |
||||
124 | |||||
125 | /** |
||||
126 | * Set response code. |
||||
127 | * |
||||
128 | * @param int $code |
||||
129 | * |
||||
130 | * @return Response |
||||
131 | */ |
||||
132 | public function setCode(int $code): self |
||||
133 | { |
||||
134 | if (!array_key_exists($code, Http::STATUS_CODES)) { |
||||
135 | throw new Exception('invalid http code set'); |
||||
136 | } |
||||
137 | |||||
138 | $this->code = $code; |
||||
139 | |||||
140 | return $this; |
||||
141 | } |
||||
142 | |||||
143 | /** |
||||
144 | * Get response code. |
||||
145 | * |
||||
146 | * @return int |
||||
147 | */ |
||||
148 | public function getCode(): int |
||||
149 | { |
||||
150 | return $this->code; |
||||
151 | } |
||||
152 | |||||
153 | /** |
||||
154 | * Set body. |
||||
155 | * |
||||
156 | * @param mixed $body |
||||
157 | * |
||||
158 | * @return Response |
||||
159 | */ |
||||
160 | public function setBody($body): self |
||||
161 | { |
||||
162 | $this->body = $body; |
||||
163 | |||||
164 | return $this; |
||||
165 | } |
||||
166 | |||||
167 | /** |
||||
168 | * Get body. |
||||
169 | * |
||||
170 | * @return string |
||||
171 | */ |
||||
172 | public function getBody() |
||||
173 | { |
||||
174 | return $this->body; |
||||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * Sends the actual response. |
||||
179 | */ |
||||
180 | public function send(): void |
||||
181 | { |
||||
182 | $status = Http::STATUS_CODES[$this->code]; |
||||
183 | $this->sendHeaders(); |
||||
184 | header('HTTP/1.0 '.$this->code.' '.$status, true, $this->code); |
||||
185 | |||||
186 | if (null === $this->body || 204 === $this->code) { |
||||
187 | return; |
||||
188 | } |
||||
189 | |||||
190 | if ($this->body instanceof Closure) { |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
191 | $body = $this->body->call($this); |
||||
0 ignored issues
–
show
|
|||||
192 | |||||
193 | return; |
||||
194 | } |
||||
195 | $body = $this->body; |
||||
196 | |||||
197 | switch ($this->output_format) { |
||||
198 | case null: |
||||
199 | break; |
||||
200 | default: |
||||
201 | case 'json': |
||||
202 | echo $this->asJSON($body); |
||||
203 | |||||
204 | break; |
||||
205 | case 'xml': |
||||
206 | echo $this->asXML($body); |
||||
207 | |||||
208 | break; |
||||
209 | case 'text': |
||||
210 | echo $body; |
||||
211 | |||||
212 | break; |
||||
213 | } |
||||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * Get output format. |
||||
218 | * |
||||
219 | * @return string |
||||
220 | */ |
||||
221 | public function getOutputFormat(): string |
||||
222 | { |
||||
223 | return $this->output_format; |
||||
224 | } |
||||
225 | |||||
226 | /** |
||||
227 | * Convert response to human readable output. |
||||
228 | * |
||||
229 | * @param bool $format |
||||
230 | * |
||||
231 | * @return Response |
||||
232 | */ |
||||
233 | public function setPrettyFormat(bool $format): self |
||||
234 | { |
||||
235 | $this->pretty_format = (bool) $format; |
||||
236 | |||||
237 | return $this; |
||||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * Set header Content-Length $body. |
||||
242 | * |
||||
243 | * @param string $body |
||||
244 | * |
||||
245 | * @return Response |
||||
246 | */ |
||||
247 | public function setContentLength(string $body): self |
||||
248 | { |
||||
249 | header('Content-Length: '.strlen($body)); |
||||
250 | |||||
251 | return $this; |
||||
252 | } |
||||
253 | |||||
254 | /** |
||||
255 | * Converts $body to pretty json. |
||||
256 | * |
||||
257 | * @param mixed $body |
||||
258 | * |
||||
259 | * @return string |
||||
260 | */ |
||||
261 | public function asJSON($body): string |
||||
262 | { |
||||
263 | if ($this->pretty_format) { |
||||
264 | $result = json_encode($body, JSON_PRETTY_PRINT); |
||||
265 | } else { |
||||
266 | $result = json_encode($body); |
||||
267 | } |
||||
268 | |||||
269 | if (false === $result) { |
||||
270 | return ''; |
||||
271 | } |
||||
272 | |||||
273 | $this->setContentLength($result); |
||||
274 | |||||
275 | return $result; |
||||
276 | } |
||||
277 | |||||
278 | /** |
||||
279 | * Converts mixed data to XML. |
||||
280 | * |
||||
281 | * @param mixed $data |
||||
282 | * @param SimpleXMLElement $xml |
||||
283 | * @param string $child_name |
||||
284 | * |
||||
285 | * @return string |
||||
286 | */ |
||||
287 | public function toXML($data, SimpleXMLElement $xml, string $child_name): string |
||||
288 | { |
||||
289 | if (is_array($data)) { |
||||
290 | foreach ($data as $k => $v) { |
||||
291 | if (is_array($v)) { |
||||
292 | (is_int($k)) ? $this->toXML($v, $xml->addChild($child_name), $v) : $this->toXML($v, $xml->addChild(strtolower($k)), $child_name); |
||||
0 ignored issues
–
show
$v of type array is incompatible with the type string expected by parameter $child_name of Micro\Http\Response::toXML() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
293 | } else { |
||||
294 | (is_int($k)) ? $xml->addChild($child_name, $v) : $xml->addChild(strtolower($k), $v); |
||||
295 | } |
||||
296 | } |
||||
297 | } else { |
||||
298 | $xml->addChild($child_name, $data); |
||||
299 | } |
||||
300 | |||||
301 | return $xml->asXML(); |
||||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * Converts response to xml. |
||||
306 | * |
||||
307 | * @param mixed $body |
||||
308 | * |
||||
309 | * @return string |
||||
310 | */ |
||||
311 | public function asXML($body): string |
||||
312 | { |
||||
313 | $root = new SimpleXMLElement('<response></response>'); |
||||
314 | $raw = $this->toXML($body, $root, 'node'); |
||||
315 | |||||
316 | if ($this->pretty_format) { |
||||
317 | $raw = $this->prettyXml($raw); |
||||
318 | } |
||||
319 | |||||
320 | $this->setContentLength($raw); |
||||
321 | |||||
322 | return $raw; |
||||
323 | } |
||||
324 | |||||
325 | /** |
||||
326 | * Pretty formatted xml. |
||||
327 | * |
||||
328 | * @param string $xml |
||||
329 | * |
||||
330 | * @return string |
||||
331 | */ |
||||
332 | public function prettyXml(string $xml): string |
||||
333 | { |
||||
334 | $domxml = new \DOMDocument('1.0'); |
||||
335 | $domxml->preserveWhiteSpace = false; |
||||
336 | $domxml->formatOutput = true; |
||||
337 | $domxml->loadXML($xml); |
||||
338 | |||||
339 | return $domxml->saveXML(); |
||||
340 | } |
||||
341 | |||||
342 | /** |
||||
343 | * Set the current output format. |
||||
344 | * |
||||
345 | * @param string $format |
||||
346 | * |
||||
347 | * @return Response |
||||
348 | */ |
||||
349 | public function setOutputFormat(?string $format = null): self |
||||
350 | { |
||||
351 | if (null === $format) { |
||||
352 | $this->output_format = null; |
||||
353 | |||||
354 | return $this; |
||||
355 | } |
||||
356 | |||||
357 | if (!array_key_exists($format, self::OUTPUT_FORMATS)) { |
||||
358 | throw new Exception('invalid output format given'); |
||||
359 | } |
||||
360 | |||||
361 | $this->setHeader('Content-Type', self::OUTPUT_FORMATS[$format]); |
||||
362 | $this->output_format = $format; |
||||
363 | |||||
364 | return $this; |
||||
365 | } |
||||
366 | |||||
367 | /** |
||||
368 | * Setup formats. |
||||
369 | * |
||||
370 | * @return Response |
||||
371 | */ |
||||
372 | public function setupFormats(): self |
||||
373 | { |
||||
374 | $pretty = array_key_exists('pretty', $_GET) && ('false' !== $_GET['pretty'] && '0' !== $_GET['pretty']); |
||||
375 | $this->setPrettyFormat($pretty); |
||||
376 | |||||
377 | //through HTTP_ACCEPT |
||||
378 | if (isset($_SERVER['HTTP_ACCEPT']) && false === strpos($_SERVER['HTTP_ACCEPT'], '*/*')) { |
||||
379 | foreach (self::OUTPUT_FORMATS as $format => $type) { |
||||
380 | if (false !== strpos($_SERVER['HTTP_ACCEPT'], $type)) { |
||||
381 | $this->output_format = $format; |
||||
382 | |||||
383 | break; |
||||
384 | } |
||||
385 | } |
||||
386 | } |
||||
387 | |||||
388 | $this->setOutputFormat($this->output_format); |
||||
389 | |||||
390 | return $this; |
||||
391 | } |
||||
392 | } |
||||
393 |