Completed
Push — master ( 9563e0...7e0ae8 )
by Raffael
02:11
created

Response::send()   D

Complexity

Conditions 9
Paths 32

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 4.909
c 0
b 0
f 0
cc 9
eloc 26
nc 32
nop 0
1
<?php
2
declare(strict_types = 1);
3
4
/**
5
 * Micro
6
 *
7
 * @author    Raffael Sahli <[email protected]>
8
 * @copyright Copyright (c) 2017 gyselroth GmbH (https://gyselroth.com)
9
 * @license   MIT https://opensource.org/licenses/MIT
10
 */
11
12
namespace Micro\Http;
13
14
use \Micro\Http;
15
use \Closure;
16
17
class Response
18
{
19
    /**
20
     * Output format
21
     *
22
     * @var string
23
     */
24
    protected $output_format = 'json';
25
26
27
    /**
28
     * Possible output formats
29
     */
30
    const OUTPUT_FORMATS = ['json', 'xml', 'text'];
31
32
33
    /**
34
     * Human readable output
35
     *
36
     * @var bool
37
     */
38
    protected $pretty_format = false;
39
40
41
    /**
42
     * Headers
43
     *
44
     * @var array
45
     */
46
    protected $headers = [];
47
48
49
    /**
50
     * Code
51
     *
52
     * @var int
53
     */
54
    protected $code = 200;
55
56
57
    /**
58
     * Body
59
     *
60
     * @var string
61
     */
62
    protected $body;
63
64
65
    /**
66
     * body only
67
     *
68
     * @var bool
69
     */
70
    protected $body_only = false;
71
72
73
    /**
74
     * Init response
75
     *
76
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
77
     */
78
    public function __construct()
79
    {
80
        $this->setupFormats();
81
    }
82
83
84
    /**
85
     * Set header
86
     *
87
     * @param   string $header
88
     * @param   string $value
89
     * @return  Response
90
     */
91
    public function setHeader(string $header, string $value): Response
92
    {
93
        $this->headers[$header] = $value;
94
        return $this;
95
    }
96
97
98
    /**
99
     * Get headers
100
     *
101
     * @return array
102
     */
103
    public function getHeaders(): array
104
    {
105
        return $this->headers;
106
    }
107
108
    
109
    /**
110
     * Send headers
111
     *
112
     * @return  Response
113
     */
114
    public function sendHeaders(): Response
115
    {
116
        foreach ($this->headers as $header => $value) {
117
            header($header.': '.$value);
118
        }
119
120
        return $this;
121
    }
122
    
123
124
    /**
125
     * Set response code
126
     *
127
     * @param   int $code
128
     * @return  Response
129
     */
130
    public function setCode(int $code): Response
131
    {
132
        if (!array_key_exists($code, Http::STATUS_CODES)) {
133
            throw new Exception('invalid http code set');
134
        }
135
        
136
        $this->code = $code;
137
        return $this;
138
    }
139
    
140
141
    /**
142
     * Get response code
143
     *
144
     * @return int
145
     */
146
    public function getCode(): int
147
    {
148
        return $this->code;
149
    }
150
 
151
152
    /**
153
     * Set body
154
     *
155
     * @param  mixed $body
156
     * @param  bool $body_only
157
     * @return Response
158
     */
159
    public function setBody($body, bool $body_only = false): Response
160
    {
161
        $this->body = $body;
162
        $this->body_only = $body_only;
163
        return $this;
164
    }
165
166
    
167
    /**
168
     * Get body
169
     *
170
     * @return string
171
     */
172
    public function getBody()
173
    {
174
        return $this->body;
175
    }
176
177
178
    /**
179
     * Sends the actual response.
180
     *
181
     * @return  void
182
     */
183
    public function send(): void
184
    {
185
        $this->sendHeaders();
186
        $status = Http::STATUS_CODES[$this->code];
187
        header('HTTP/1.0 '.$this->code.' '.$status, true, $this->code);
188
189
        if ($this->body === null && $this->code == 204) {
190
            $this->terminate();
191
        }
192
        
193
        if($this->body instanceof Closure) {
194
            $body = $this->body();
0 ignored issues
show
Bug introduced by
The method body() does not seem to exist on object<Micro\Http\Response>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
195
        } else {
196
            $body = $this->body;
197
        }
198
            
199
        if ($this->body_only === false && $this->output_format !== 'text') {
200
            $body = ['data' => $body];
201
            $body['status'] = intval($this->code);
202
            $body = array_reverse($body, true);
203
        }
204
        
205
        switch ($this->output_format) {
206
            default:
207
            case 'json':
208
                echo $this->asJSON($body);
209
            break;
210
            
211
            case 'xml':
212
                echo $this->asXML($body);
213
            break;
214
215
            case 'text':
216
                echo $body;
217
            break;
218
        }
219
220
        $this->terminate();
221
    }
222
223
224
    /**
225
     * Get output format
226
     *
227
     * @return string
228
     */
229
    public function getOutputFormat(): string
230
    {
231
        return $this->output_format;
232
    }
233
234
235
    /**
236
     * Convert response to human readable output
237
     *
238
     * @param   bool $format
239
     * @return  Response
240
     */
241
    public function setPrettyFormat(bool $format): Response
242
    {
243
        $this->pretty_format = (bool)$format;
244
        return $this;
245
    }
246
247
248
    /**
249
     * Set header Content-Length $body.
250
     *
251
     * @param  string $body
252
     * @return Response
253
     */
254
    public function setContentLength(string $body): Response
255
    {
256
        header('Content-Length: '.strlen($body));
257
        return $this;
258
    }
259
260
261
    /**
262
     * Converts $body to pretty json.
263
     *
264
     * @param  mixed $body
265
     * @return string
266
     */
267
    public function asJSON($body): string
268
    {
269
        header('Content-Type: application/json; charset=utf-8');
270
271
        if ($this->pretty_format) {
272
            $result = json_encode($body, JSON_PRETTY_PRINT);
273
        } else {
274
            $result = json_encode($body);
275
        }
276
277
        $this->setContentLength($result);
278
279
        return $result;
280
    }
281
282
283
    /**
284
     * Converts mixed data to XML
285
     *
286
     * @param    mixed $data
287
     * @param    SimpleXMLElement $xml
288
     * @param    string $child_name
289
     * @return   string
290
     */
291
    public function toXML($data, Config $xml, string $child_name): string
292
    {
293
        if (is_array($data)) {
294
            foreach ($data as $k => $v) {
295
                if (is_array($v)) {
296
                    (is_int($k)) ? $this->toXML($v, $xml->addChild($child_name), $v) : $this->toXML($v, $xml->addChild(strtolower($k)), $child_name);
0 ignored issues
show
Documentation introduced by
$v is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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