Completed
Push — master ( 76ae44...61569d )
by Raffael
01:46
created

Response::removeHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
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 = [
31
        'json' => 'application/json; charset=utf-8', 
32
        'xml'  => 'application/xml; charset=utf-8', 
33
        'text' => 'text/html; charset=utf-8'
34
    ];
35
36
37
    /**
38
     * Human readable output
39
     *
40
     * @var bool
41
     */
42
    protected $pretty_format = false;
43
44
45
    /**
46
     * Headers
47
     *
48
     * @var array
49
     */
50
    protected $headers = [
51
        'Content-Type' => self::OUTPUT_FORMATS['json']
52
    ];
53
54
55
    /**
56
     * Code
57
     *
58
     * @var int
59
     */
60
    protected $code = 200;
61
62
63
    /**
64
     * Body
65
     *
66
     * @var string
67
     */
68
    protected $body;
69
70
71
    /**
72
     * body only
73
     *
74
     * @var bool
75
     */
76
    protected $body_only = false;
77
78
79
    /**
80
     * Init response
81
     *
82
     * @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...
83
     */
84
    public function __construct()
85
    {
86
        $this->setupFormats();
87
    }
88
89
90
    /**
91
     * Set header
92
     *
93
     * @param   string $header
94
     * @param   string $value
95
     * @return  Response
96
     */
97
    public function setHeader(string $header, string $value): Response
98
    {
99
        $this->headers[$header] = $value;
100
        return $this;
101
    }
102
    
103
104
    /**
105
     * Delete header
106
     *
107
     * @param   string $header
108
     * @return  Response
109
     */
110
    public function removeHeader(string $header): Response
111
    {
112
        if(isset($this->headers[$header])) {
113
            unset($this->headers[$header]);
114
        }
115
116
        return $this;
117
    }
118
119
120
    /**
121
     * Get headers
122
     *
123
     * @return array
124
     */
125
    public function getHeaders(): array
126
    {
127
        return $this->headers;
128
    }
129
130
    
131
    /**
132
     * Send headers
133
     *
134
     * @return  Response
135
     */
136
    public function sendHeaders(): Response
137
    {
138
        foreach ($this->headers as $header => $value) {
139
            header($header.': '.$value);
140
        }
141
142
        return $this;
143
    }
144
    
145
146
    /**
147
     * Set response code
148
     *
149
     * @param   int $code
150
     * @return  Response
151
     */
152
    public function setCode(int $code): Response
153
    {
154
        if (!array_key_exists($code, Http::STATUS_CODES)) {
155
            throw new Exception('invalid http code set');
156
        }
157
        
158
        $this->code = $code;
159
        return $this;
160
    }
161
    
162
163
    /**
164
     * Get response code
165
     *
166
     * @return int
167
     */
168
    public function getCode(): int
169
    {
170
        return $this->code;
171
    }
172
 
173
174
    /**
175
     * Set body
176
     *
177
     * @param  mixed $body
178
     * @param  bool $body_only
179
     * @return Response
180
     */
181
    public function setBody($body, bool $body_only = false): Response
182
    {
183
        $this->body = $body;
184
        $this->body_only = $body_only;
185
        return $this;
186
    }
187
188
    
189
    /**
190
     * Get body
191
     *
192
     * @return string
193
     */
194
    public function getBody()
195
    {
196
        return $this->body;
197
    }
198
199
200
    /**
201
     * Sends the actual response.
202
     *
203
     * @return  void
204
     */
205
    public function send(): void
206
    {
207
        $this->sendHeaders();
208
        $status = Http::STATUS_CODES[$this->code];
209
        header('HTTP/1.0 '.$this->code.' '.$status, true, $this->code);
210
211
        if ($this->body === null && $this->code == 204) {
212
            $this->terminate();
213
        }
214
        
215
        if($this->body instanceof Closure) {
216
            $body = $this->body->call($this);
217
        } else {
218
            $body = $this->body;
219
        }
220
            
221
        if ($this->body_only === false && $this->output_format !== 'text') {
222
            $body = ['data' => $body];
223
            $body['status'] = intval($this->code);
224
            $body = array_reverse($body, true);
225
        }
226
        
227
        switch ($this->output_format) {
228
            case 'json':
229
                echo $this->asJSON($body);
230
            break;
231
            
232
            case 'xml':
233
                echo $this->asXML($body);
234
            break;
235
236
            case 'text':
237
                echo $body;
238
            break;
239
        }
240
241
        $this->terminate();
242
    }
243
244
245
    /**
246
     * Get output format
247
     *
248
     * @return string
249
     */
250
    public function getOutputFormat(): string
251
    {
252
        return $this->output_format;
253
    }
254
255
256
    /**
257
     * Convert response to human readable output
258
     *
259
     * @param   bool $format
260
     * @return  Response
261
     */
262
    public function setPrettyFormat(bool $format): Response
263
    {
264
        $this->pretty_format = (bool)$format;
265
        return $this;
266
    }
267
268
269
    /**
270
     * Set header Content-Length $body.
271
     *
272
     * @param  string $body
273
     * @return Response
274
     */
275
    public function setContentLength(string $body): Response
276
    {
277
        header('Content-Length: '.strlen($body));
278
        return $this;
279
    }
280
281
282
    /**
283
     * Converts $body to pretty json.
284
     *
285
     * @param  mixed $body
286
     * @return string
287
     */
288
    public function asJSON($body): string
289
    {
290
        if ($this->pretty_format) {
291
            $result = json_encode($body, JSON_PRETTY_PRINT);
292
        } else {
293
            $result = json_encode($body);
294
        }
295
296
        $this->setContentLength($result);
297
298
        return $result;
299
    }
300
301
302
    /**
303
     * Converts mixed data to XML
304
     *
305
     * @param    mixed $data
306
     * @param    SimpleXMLElement $xml
307
     * @param    string $child_name
308
     * @return   string
309
     */
310
    public function toXML($data, Config $xml, string $child_name): string
311
    {
312
        if (is_array($data)) {
313
            foreach ($data as $k => $v) {
314
                if (is_array($v)) {
315
                    (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...
316
                } else {
317
                    (is_int($k)) ? $xml->addChild($child_name, $v) : $xml->addChild(strtolower($k), $v);
318
                }
319
            }
320
        } else {
321
            $xml->addChild($child_name, $data);
322
        }
323
324
        return $xml->asXML();
325
    }
326
327
328
    /**
329
     * Converts response to xml.
330
     *
331
     * @param   mixed $body
332
     * @return  string
333
     */
334
    public function asXML($body): string
335
    {
336
        $root = new Config('<response></response>');
337
        $raw = $this->toXML($body, $root, 'node');
338
        
339
        if ($this->pretty_format) {
340
            $raw = $this->prettyXml($raw);
341
        }
342
343
        $this->setContentLength($raw);
344
        return $raw;
345
    }
346
347
348
    /**
349
     * Pretty formatted xml
350
     *
351
     * @param   string $xml
352
     * @return  string
353
     */
354
    public function prettyXml(string $xml): string
355
    {
356
        $domxml = new \DOMDocument('1.0');
357
        $domxml->preserveWhiteSpace = false;
358
        $domxml->formatOutput = true;
359
        $domxml->loadXML($xml);
360
361
        return $domxml->saveXML();
362
    }
363
364
365
    /**
366
     * Set the current output format.
367
     *
368
     * @param  string $format
369
     * @return Response
370
     */
371
    public function setOutputFormat(?string $format=null): Response
372
    {
373
        if($format === null) {
374
            $this->output_format = null;
375
            return $this->removeHeader('Content-Type');
376
        }
377
378
        if(!array_key_exists($format, self::OUTPUT_FORMATS)) {
379
            throw new Exception('invalid output format given');
380
        }
381
382
        $this->setHeader('Content-Type', self::OUTPUT_FORMATS[$format]);
383
        $this->output_format = $format;
384
        return $this;
385
    }
386
387
    
388
    /**
389
     * Abort after response
390
     *
391
     * @return void
392
     */
393
    public function terminate(): void
394
    {
395
        exit();
396
    }
397
398
399
    /**
400
     * Setup formats.
401
     *
402
     * @return Response
403
     */
404
    public function setupFormats(): Response
405
    {
406
        $pretty = array_key_exists('pretty', $_GET) && ($_GET['pretty'] != 'false' && $_GET['pretty'] != '0');
407
        $this->setPrettyFormat($pretty);
408
409
        //through HTTP_ACCEPT
410
        if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], '*/*') === false) {
411
            foreach (self::OUTPUT_FORMATS as $format) {
412
                if (strpos($_SERVER['HTTP_ACCEPT'], $format) !== false) {
413
                    $this->output_format = $format;
414
                    break;
415
                }
416
            }
417
        }
418
419
        return $this;
420
    }
421
}
422