Passed
Push — master ( 23e1ba...84f37d )
by Raffael
04:32
created

Response::send()   D

Complexity

Conditions 9
Paths 22

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 23
nc 22
nop 0
dl 0
loc 36
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Micro
7
 *
8
 * @author      Raffael Sahli <[email protected]>
9
 * @copyright   Copryright (c) 2015-2017 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
        if (!isset($this->headers['Content-Type'])) {
183
            //    $this->setOutputFormat(null);
184
        }
185
186
        $status = Http::STATUS_CODES[$this->code];
187
        $this->sendHeaders();
188
        header('HTTP/1.0 '.$this->code.' '.$status, true, $this->code);
189
190
        if (null === $this->body || 204 === $this->code) {
191
            return;
192
        }
193
194
        if ($this->body instanceof Closure) {
0 ignored issues
show
introduced by
The condition $this->body instanceof Closure can never be true since $this->body is never a sub-type of Closure.
Loading history...
195
            $body = $this->body->call($this);
196
        } else {
197
            $body = $this->body;
198
        }
199
200
        switch ($this->output_format) {
201
            case null:
202
            break;
203
            default:
204
            case 'json':
205
                echo $this->asJSON($body);
206
207
            break;
208
            case 'xml':
209
                echo $this->asXML($body);
210
211
            break;
212
            case 'text':
213
                echo $body;
214
215
            break;
216
        }
217
    }
218
219
    /**
220
     * Get output format.
221
     *
222
     * @return string
223
     */
224
    public function getOutputFormat(): string
225
    {
226
        return $this->output_format;
227
    }
228
229
    /**
230
     * Convert response to human readable output.
231
     *
232
     * @param bool $format
233
     *
234
     * @return Response
235
     */
236
    public function setPrettyFormat(bool $format): self
237
    {
238
        $this->pretty_format = (bool) $format;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Set header Content-Length $body.
245
     *
246
     * @param string $body
247
     *
248
     * @return Response
249
     */
250
    public function setContentLength(string $body): self
251
    {
252
        header('Content-Length: '.strlen($body));
253
254
        return $this;
255
    }
256
257
    /**
258
     * Converts $body to pretty json.
259
     *
260
     * @param mixed $body
261
     *
262
     * @return string
263
     */
264
    public function asJSON($body): string
265
    {
266
        if ($this->pretty_format) {
267
            $result = json_encode($body, JSON_PRETTY_PRINT);
268
        } else {
269
            $result = json_encode($body);
270
        }
271
272
        $this->setContentLength($result);
273
274
        return $result;
275
    }
276
277
    /**
278
     * Converts mixed data to XML.
279
     *
280
     * @param mixed            $data
281
     * @param SimpleXMLElement $xml
282
     * @param string           $child_name
283
     *
284
     * @return string
285
     */
286
    public function toXML($data, SimpleXMLElement $xml, string $child_name): string
287
    {
288
        if (is_array($data)) {
289
            foreach ($data as $k => $v) {
290
                if (is_array($v)) {
291
                    (is_int($k)) ? $this->toXML($v, $xml->addChild($child_name), $v) : $this->toXML($v, $xml->addChild(strtolower($k)), $child_name);
0 ignored issues
show
Bug introduced by
$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 ignore-type  annotation

291
                    (is_int($k)) ? $this->toXML($v, $xml->addChild($child_name), /** @scrutinizer ignore-type */ $v) : $this->toXML($v, $xml->addChild(strtolower($k)), $child_name);
Loading history...
292
                } else {
293
                    (is_int($k)) ? $xml->addChild($child_name, $v) : $xml->addChild(strtolower($k), $v);
294
                }
295
            }
296
        } else {
297
            $xml->addChild($child_name, $data);
298
        }
299
300
        return $xml->asXML();
301
    }
302
303
    /**
304
     * Converts response to xml.
305
     *
306
     * @param mixed $body
307
     *
308
     * @return string
309
     */
310
    public function asXML($body): string
311
    {
312
        $root = new SimpleXMLElement('<response></response>');
313
        $raw = $this->toXML($body, $root, 'node');
314
315
        if ($this->pretty_format) {
316
            $raw = $this->prettyXml($raw);
317
        }
318
319
        $this->setContentLength($raw);
320
321
        return $raw;
322
    }
323
324
    /**
325
     * Pretty formatted xml.
326
     *
327
     * @param string $xml
328
     *
329
     * @return string
330
     */
331
    public function prettyXml(string $xml): string
332
    {
333
        $domxml = new \DOMDocument('1.0');
334
        $domxml->preserveWhiteSpace = false;
335
        $domxml->formatOutput = true;
336
        $domxml->loadXML($xml);
337
338
        return $domxml->saveXML();
339
    }
340
341
    /**
342
     * Set the current output format.
343
     *
344
     * @param string $format
345
     *
346
     * @return Response
347
     */
348
    public function setOutputFormat(?string $format = null): self
349
    {
350
        if (null === $format) {
351
            $this->output_format = null;
352
353
            return $this;
354
        }
355
356
        if (!array_key_exists($format, self::OUTPUT_FORMATS)) {
357
            throw new Exception('invalid output format given');
358
        }
359
360
        $this->setHeader('Content-Type', self::OUTPUT_FORMATS[$format]);
361
        $this->output_format = $format;
362
363
        return $this;
364
    }
365
366
    /**
367
     * Setup formats.
368
     *
369
     * @return Response
370
     */
371
    public function setupFormats(): self
372
    {
373
        $pretty = array_key_exists('pretty', $_GET) && ('false' !== $_GET['pretty'] && '0' !== $_GET['pretty']);
374
        $this->setPrettyFormat($pretty);
375
376
        //through HTTP_ACCEPT
377
        if (isset($_SERVER['HTTP_ACCEPT']) && false === strpos($_SERVER['HTTP_ACCEPT'], '*/*')) {
378
            foreach (self::OUTPUT_FORMATS as $format) {
379
                if (false !== strpos($_SERVER['HTTP_ACCEPT'], $format)) {
380
                    $this->output_format = $format;
381
382
                    break;
383
                }
384
            }
385
        }
386
387
        $this->setOutputFormat($this->output_format);
388
389
        return $this;
390
    }
391
}
392