Response   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 409
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 1
dl 0
loc 409
rs 8.439
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getBody() 0 4 1
A __construct() 0 4 1
A setHeader() 0 5 1
A removeHeader() 0 8 2
A getHeaders() 0 4 1
A sendHeaders() 0 8 2
A setCode() 0 9 2
A getCode() 0 4 1
A setBody() 0 8 1
A getOutputFormat() 0 4 1
A setPrettyFormat() 0 5 1
A setContentLength() 0 5 1
A asJSON() 0 12 2
B toXML() 0 16 6
A asXML() 0 12 2
A prettyXml() 0 9 1
A setOutputFormat() 0 15 3
A terminate() 0 4 1
B setupFormats() 0 17 7
D send() 0 42 10

How to fix   Complexity   

Complex Class

Complex classes like Response often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Response, and based on these observations, apply Extract Interface, too.

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
52
53
    /**
54
     * Code
55
     *
56
     * @var int
57
     */
58
    protected $code = 200;
59
60
61
    /**
62
     * Body
63
     *
64
     * @var string
65
     */
66
    protected $body;
67
68
69
    /**
70
     * body only
71
     *
72
     * @var bool
73
     */
74
    protected $body_only = false;
75
76
77
    /**
78
     * Init response
79
     *
80
     * @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...
81
     */
82
    public function __construct()
83
    {
84
        $this->setupFormats();
85
    }
86
87
88
    /**
89
     * Set header
90
     *
91
     * @param   string $header
92
     * @param   string $value
93
     * @return  Response
94
     */
95
    public function setHeader(string $header, string $value): Response
96
    {
97
        $this->headers[$header] = $value;
98
        return $this;
99
    }
100
101
102
    /**
103
     * Delete header
104
     *
105
     * @param   string $header
106
     * @return  Response
107
     */
108
    public function removeHeader(string $header): Response
109
    {
110
        if(isset($this->headers[$header])) {
111
            unset($this->headers[$header]);
112
        }
113
114
        return $this;
115
    }
116
117
118
    /**
119
     * Get headers
120
     *
121
     * @return array
122
     */
123
    public function getHeaders(): array
124
    {
125
        return $this->headers;
126
    }
127
128
129
    /**
130
     * Send headers
131
     *
132
     * @return  Response
133
     */
134
    public function sendHeaders(): Response
135
    {
136
        foreach ($this->headers as $header => $value) {
137
            header($header.': '.$value);
138
        }
139
140
        return $this;
141
    }
142
143
144
    /**
145
     * Set response code
146
     *
147
     * @param   int $code
148
     * @return  Response
149
     */
150
    public function setCode(int $code): Response
151
    {
152
        if (!array_key_exists($code, Http::STATUS_CODES)) {
153
            throw new Exception('invalid http code set');
154
        }
155
156
        $this->code = $code;
157
        return $this;
158
    }
159
160
161
    /**
162
     * Get response code
163
     *
164
     * @return int
165
     */
166
    public function getCode(): int
167
    {
168
        return $this->code;
169
    }
170
171
172
    /**
173
     * Set body
174
     *
175
     * @param  mixed $body
176
     * @param  bool $body_only
177
     * @return Response
178
     */
179
    public function setBody($body, bool $body_only = false): Response
180
    {
181
        $this->body = $body;
182
        $this->body_only = $body_only;
183
        $this->setOutputFormat($this->output_format);
184
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
        $status = Http::STATUS_CODES[$this->code];
208
        $this->sendHeaders();
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 null:
229
            break;
230
231
            default:
232
            case 'json':
0 ignored issues
show
Unused Code introduced by
case 'json': echo $t...JSON($body); break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
233
                echo $this->asJSON($body);
234
            break;
235
236
            case 'xml':
237
                echo $this->asXML($body);
238
            break;
239
240
            case 'text':
241
                echo $body;
242
            break;
243
        }
244
245
        //$this->terminate();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
246
    }
247
248
249
    /**
250
     * Get output format
251
     *
252
     * @return string
253
     */
254
    public function getOutputFormat(): string
255
    {
256
        return $this->output_format;
257
    }
258
259
260
    /**
261
     * Convert response to human readable output
262
     *
263
     * @param   bool $format
264
     * @return  Response
265
     */
266
    public function setPrettyFormat(bool $format): Response
267
    {
268
        $this->pretty_format = (bool)$format;
269
        return $this;
270
    }
271
272
273
    /**
274
     * Set header Content-Length $body.
275
     *
276
     * @param  string $body
277
     * @return Response
278
     */
279
    public function setContentLength(string $body): Response
280
    {
281
        header('Content-Length: '.strlen($body));
282
        return $this;
283
    }
284
285
286
    /**
287
     * Converts $body to pretty json.
288
     *
289
     * @param  mixed $body
290
     * @return string
291
     */
292
    public function asJSON($body): string
293
    {
294
        if ($this->pretty_format) {
295
            $result = json_encode($body, JSON_PRETTY_PRINT);
296
        } else {
297
            $result = json_encode($body);
298
        }
299
300
        $this->setContentLength($result);
301
302
        return $result;
303
    }
304
305
306
    /**
307
     * Converts mixed data to XML
308
     *
309
     * @param    mixed $data
310
     * @param    SimpleXMLElement $xml
311
     * @param    string $child_name
312
     * @return   string
313
     */
314
    public function toXML($data, Config $xml, string $child_name): string
315
    {
316
        if (is_array($data)) {
317
            foreach ($data as $k => $v) {
318
                if (is_array($v)) {
319
                    (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...
320
                } else {
321
                    (is_int($k)) ? $xml->addChild($child_name, $v) : $xml->addChild(strtolower($k), $v);
322
                }
323
            }
324
        } else {
325
            $xml->addChild($child_name, $data);
326
        }
327
328
        return $xml->asXML();
329
    }
330
331
332
    /**
333
     * Converts response to xml.
334
     *
335
     * @param   mixed $body
336
     * @return  string
337
     */
338
    public function asXML($body): string
339
    {
340
        $root = new Config('<response></response>');
341
        $raw = $this->toXML($body, $root, 'node');
342
343
        if ($this->pretty_format) {
344
            $raw = $this->prettyXml($raw);
345
        }
346
347
        $this->setContentLength($raw);
348
        return $raw;
349
    }
350
351
352
    /**
353
     * Pretty formatted xml
354
     *
355
     * @param   string $xml
356
     * @return  string
357
     */
358
    public function prettyXml(string $xml): string
359
    {
360
        $domxml = new \DOMDocument('1.0');
361
        $domxml->preserveWhiteSpace = false;
362
        $domxml->formatOutput = true;
363
        $domxml->loadXML($xml);
364
365
        return $domxml->saveXML();
366
    }
367
368
369
    /**
370
     * Set the current output format.
371
     *
372
     * @param  string $format
373
     * @return Response
374
     */
375
    public function setOutputFormat(?string $format=null): Response
376
    {
377
        if($format === null) {
378
            $this->output_format = null;
379
            return $this->removeHeader('Content-Type');
380
        }
381
382
        if(!array_key_exists($format, self::OUTPUT_FORMATS)) {
383
            throw new Exception('invalid output format given');
384
        }
385
386
        $this->setHeader('Content-Type', self::OUTPUT_FORMATS[$format]);
387
        $this->output_format = $format;
388
        return $this;
389
    }
390
391
392
    /**
393
     * Abort after response
394
     *
395
     * @return void
396
     */
397
    public function terminate(): void
398
    {
399
        exit();
400
    }
401
402
403
    /**
404
     * Setup formats.
405
     *
406
     * @return Response
407
     */
408
    public function setupFormats(): Response
409
    {
410
        $pretty = array_key_exists('pretty', $_GET) && ($_GET['pretty'] != 'false' && $_GET['pretty'] != '0');
411
        $this->setPrettyFormat($pretty);
412
413
        //through HTTP_ACCEPT
414
        if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], '*/*') === false) {
415
            foreach (self::OUTPUT_FORMATS as $format) {
416
                if (strpos($_SERVER['HTTP_ACCEPT'], $format) !== false) {
417
                    $this->output_format = $format;
418
                    break;
419
                }
420
            }
421
        }
422
423
        return $this;
424
    }
425
}
426