Completed
Push — master ( 93f6e0...a7ea34 )
by Aydin
27:25 queued 18:55
created

AbstractResponse   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 507
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 42
c 2
b 0
f 1
lcom 1
cbo 6
dl 0
loc 507
rs 8.2951

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A protocolVersion() 0 13 2
A body() 0 13 2
A status() 0 4 1
A headers() 0 4 1
A cookies() 0 4 1
A code() 0 13 2
A prepend() 0 9 1
A append() 0 9 1
A isLocked() 0 4 1
A requireUnlocked() 0 8 2
A lock() 0 6 1
A unlock() 0 6 1
A httpStatusLine() 0 4 1
B sendHeaders() 0 20 5
B sendCookies() 0 22 4
A sendBody() 0 6 1
B send() 0 23 4
A isSent() 0 4 1
A chunk() 0 18 3
A header() 0 6 1
A cookie() 0 20 2
A noCache() 0 7 1
A redirect() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractResponse 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 AbstractResponse, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Klein (klein.php) - A fast & flexible router for PHP
4
 *
5
 * @author      Chris O'Hara <[email protected]>
6
 * @author      Trevor Suarez (Rican7) (contributor and v2 refactorer)
7
 * @copyright   (c) Chris O'Hara
8
 * @link        https://github.com/chriso/klein.php
9
 * @license     MIT
10
 */
11
12
namespace Klein;
13
14
use Klein\DataCollection\HeaderDataCollection;
15
use Klein\DataCollection\ResponseCookieDataCollection;
16
use Klein\Exceptions\LockedResponseException;
17
use Klein\Exceptions\ResponseAlreadySentException;
18
use Klein\ResponseCookie;
19
20
/**
21
 * AbstractResponse
22
 */
23
abstract class AbstractResponse
24
{
25
26
    /**
27
     * Properties
28
     */
29
30
    /**
31
     * The default response HTTP status code
32
     *
33
     * @type int
34
     */
35
    protected static $default_status_code = 200;
36
37
    /**
38
     * The HTTP version of the response
39
     *
40
     * @type string
41
     */
42
    protected $protocol_version = '1.1';
43
44
    /**
45
     * The response body
46
     *
47
     * @type string
48
     */
49
    protected $body;
50
51
    /**
52
     * HTTP response status
53
     *
54
     * @type HttpStatus
55
     */
56
    protected $status;
57
58
    /**
59
     * HTTP response headers
60
     *
61
     * @type HeaderDataCollection
62
     */
63
    protected $headers;
64
65
    /**
66
     * HTTP response cookies
67
     *
68
     * @type ResponseCookieDataCollection
69
     */
70
    protected $cookies;
71
72
    /**
73
     * Whether or not the response is "locked" from
74
     * any further modification
75
     *
76
     * @type boolean
77
     */
78
    protected $locked = false;
79
80
    /**
81
     * Whether or not the response has been sent
82
     *
83
     * @type boolean
84
     */
85
    protected $sent = false;
86
87
    /**
88
     * Whether the response has been chunked or not
89
     *
90
     * @type boolean
91
     */
92
    public $chunked = false;
93
94
95
    /**
96
     * Methods
97
     */
98
99
    /**
100
     * Constructor
101
     *
102
     * Create a new AbstractResponse object with a dependency injected Headers instance
103
     *
104
     * @param string $body          The response body's content
105
     * @param int $status_code      The status code
106
     * @param array $headers        The response header "hash"
107
     */
108
    public function __construct($body = '', $status_code = null, array $headers = array())
109
    {
110
        $status_code   = $status_code ?: static::$default_status_code;
111
112
        // Set our body and code using our internal methods
113
        $this->body($body);
114
        $this->code($status_code);
115
116
        $this->headers = new HeaderDataCollection($headers);
117
        $this->cookies = new ResponseCookieDataCollection();
118
    }
119
120
    /**
121
     * Get (or set) the HTTP protocol version
122
     *
123
     * Simply calling this method without any arguments returns the current protocol version.
124
     * Calling with an integer argument, however, attempts to set the protocol version to what
125
     * was provided by the argument.
126
     *
127
     * @param string $protocol_version
128
     * @return string|AbstractResponse
129
     */
130
    public function protocolVersion($protocol_version = null)
131
    {
132
        if (null !== $protocol_version) {
133
            // Require that the response be unlocked before changing it
134
            $this->requireUnlocked();
135
136
            $this->protocol_version = (string) $protocol_version;
137
138
            return $this;
139
        }
140
141
        return $this->protocol_version;
142
    }
143
144
    /**
145
     * Get (or set) the response's body content
146
     *
147
     * Simply calling this method without any arguments returns the current response body.
148
     * Calling with an argument, however, sets the response body to what was provided by the argument.
149
     *
150
     * @param string $body  The body content string
151
     * @return string|AbstractResponse
152
     */
153
    public function body($body = null)
154
    {
155
        if (null !== $body) {
156
            // Require that the response be unlocked before changing it
157
            $this->requireUnlocked();
158
159
            $this->body = (string) $body;
160
161
            return $this;
162
        }
163
164
        return $this->body;
165
    }
166
167
    /**
168
     * Returns the status object
169
     *
170
     * @return \Klein\HttpStatus
171
     */
172
    public function status()
173
    {
174
        return $this->status;
175
    }
176
177
    /**
178
     * Returns the headers collection
179
     *
180
     * @return HeaderDataCollection
181
     */
182
    public function headers()
183
    {
184
        return $this->headers;
185
    }
186
187
    /**
188
     * Returns the cookies collection
189
     *
190
     * @return ResponseCookieDataCollection
191
     */
192
    public function cookies()
193
    {
194
        return $this->cookies;
195
    }
196
197
    /**
198
     * Get (or set) the HTTP response code
199
     *
200
     * Simply calling this method without any arguments returns the current response code.
201
     * Calling with an integer argument, however, attempts to set the response code to what
202
     * was provided by the argument.
203
     *
204
     * @param int $code     The HTTP status code to send
205
     * @return int|AbstractResponse
206
     */
207
    public function code($code = null)
208
    {
209
        if (null !== $code) {
210
            // Require that the response be unlocked before changing it
211
            $this->requireUnlocked();
212
213
            $this->status = new HttpStatus($code);
214
215
            return $this;
216
        }
217
218
        return $this->status->getCode();
219
    }
220
221
    /**
222
     * Prepend a string to the response's content body
223
     *
224
     * @param string $content   The string to prepend
225
     * @return AbstractResponse
226
     */
227
    public function prepend($content)
228
    {
229
        // Require that the response be unlocked before changing it
230
        $this->requireUnlocked();
231
232
        $this->body = $content . $this->body;
233
234
        return $this;
235
    }
236
237
    /**
238
     * Append a string to the response's content body
239
     *
240
     * @param string $content   The string to append
241
     * @return AbstractResponse
242
     */
243
    public function append($content)
244
    {
245
        // Require that the response be unlocked before changing it
246
        $this->requireUnlocked();
247
248
        $this->body .= $content;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Check if the response is locked
255
     *
256
     * @return boolean
257
     */
258
    public function isLocked()
259
    {
260
        return $this->locked;
261
    }
262
263
    /**
264
     * Require that the response is unlocked
265
     *
266
     * Throws an exception if the response is locked,
267
     * preventing any methods from mutating the response
268
     * when its locked
269
     *
270
     * @throws LockedResponseException  If the response is locked
271
     * @return AbstractResponse
272
     */
273
    public function requireUnlocked()
274
    {
275
        if ($this->isLocked()) {
276
            throw new LockedResponseException('Response is locked');
277
        }
278
279
        return $this;
280
    }
281
282
    /**
283
     * Lock the response from further modification
284
     *
285
     * @return AbstractResponse
286
     */
287
    public function lock()
288
    {
289
        $this->locked = true;
290
291
        return $this;
292
    }
293
294
    /**
295
     * Unlock the response from further modification
296
     *
297
     * @return AbstractResponse
298
     */
299
    public function unlock()
300
    {
301
        $this->locked = false;
302
303
        return $this;
304
    }
305
306
    /**
307
     * Generates an HTTP compatible status header line string
308
     *
309
     * Creates the string based off of the response's properties
310
     *
311
     * @return string
312
     */
313
    protected function httpStatusLine()
314
    {
315
        return sprintf('HTTP/%s %s', $this->protocol_version, $this->status);
316
    }
317
318
    /**
319
     * Send our HTTP headers
320
     *
321
     * @param boolean $cookies_also Whether or not to also send the cookies after sending the normal headers
322
     * @param boolean $override     Whether or not to override the check if headers have already been sent
323
     * @return AbstractResponse
324
     */
325
    public function sendHeaders($cookies_also = true, $override = false)
326
    {
327
        if (headers_sent() && !$override) {
328
            return $this;
329
        }
330
331
        // Send our HTTP status line
332
        header($this->httpStatusLine());
333
334
        // Iterate through our Headers data collection and send each header
335
        foreach ($this->headers as $key => $value) {
336
            header($key .': '. $value, false);
337
        }
338
339
        if ($cookies_also) {
340
            $this->sendCookies($override);
341
        }
342
343
        return $this;
344
    }
345
346
    /**
347
     * Send our HTTP response cookies
348
     *
349
     * @param boolean $override     Whether or not to override the check if headers have already been sent
350
     * @return AbstractResponse
351
     */
352
    public function sendCookies($override = false)
353
    {
354
        if (headers_sent() && !$override) {
355
            return $this;
356
        }
357
358
        // Iterate through our Cookies data collection and set each cookie natively
359
        foreach ($this->cookies as $cookie) {
360
            // Use the built-in PHP "setcookie" function
361
            setcookie(
362
                $cookie->getName(),
363
                $cookie->getValue(),
364
                $cookie->getExpire(),
365
                $cookie->getPath(),
366
                $cookie->getDomain(),
367
                $cookie->getSecure(),
368
                $cookie->getHttpOnly()
369
            );
370
        }
371
372
        return $this;
373
    }
374
375
    /**
376
     * Send our body's contents
377
     *
378
     * @return AbstractResponse
379
     */
380
    public function sendBody()
381
    {
382
        echo (string) $this->body;
383
384
        return $this;
385
    }
386
387
    /**
388
     * Send the response and lock it
389
     *
390
     * @param boolean $override             Whether or not to override the check if the response has already been sent
391
     * @throws ResponseAlreadySentException If the response has already been sent
392
     * @return AbstractResponse
393
     */
394
    public function send($override = false)
395
    {
396
        if ($this->sent && !$override) {
397
            throw new ResponseAlreadySentException('Response has already been sent');
398
        }
399
400
        // Send our response data
401
        $this->sendHeaders();
402
        $this->sendBody();
403
404
        // Lock the response from further modification
405
        $this->lock();
406
407
        // Mark as sent
408
        $this->sent = true;
409
410
        // If there running FPM, tell the process manager to finish the server request/response handling
411
        if (function_exists('fastcgi_finish_request')) {
412
            fastcgi_finish_request();
413
        }
414
415
        return $this;
416
    }
417
418
    /**
419
     * Check if the response has been sent
420
     *
421
     * @return boolean
422
     */
423
    public function isSent()
424
    {
425
        return $this->sent;
426
    }
427
428
    /**
429
     * Enable response chunking
430
     *
431
     * @link https://github.com/chriso/klein.php/wiki/Response-Chunking
432
     * @link http://bit.ly/hg3gHb
433
     * @return AbstractResponse
434
     */
435
    public function chunk()
436
    {
437
        if (false === $this->chunked) {
438
            $this->chunked = true;
439
            $this->header('Transfer-encoding', 'chunked');
440
            flush();
441
        }
442
443
        if (($body_length = strlen($this->body)) > 0) {
444
            printf("%x\r\n", $body_length);
445
            $this->sendBody();
446
            $this->body('');
447
            echo "\r\n";
448
            flush();
449
        }
450
451
        return $this;
452
    }
453
454
    /**
455
     * Sets a response header
456
     *
457
     * @param string $key       The name of the HTTP response header
458
     * @param mixed $value      The value to set the header with
459
     * @return AbstractResponse
460
     */
461
    public function header($key, $value)
462
    {
463
        $this->headers->set($key, $value);
464
465
        return $this;
466
    }
467
468
    /**
469
     * Sets a response cookie
470
     *
471
     * @param string $key           The name of the cookie
472
     * @param string $value         The value to set the cookie with
473
     * @param int $expiry           The time that the cookie should expire
474
     * @param string $path          The path of which to restrict the cookie
475
     * @param string $domain        The domain of which to restrict the cookie
476
     * @param boolean $secure       Flag of whether the cookie should only be sent over a HTTPS connection
477
     * @param boolean $httponly     Flag of whether the cookie should only be accessible over the HTTP protocol
478
     * @return AbstractResponse
479
     */
480
    public function cookie(
481
        $key,
482
        $value = '',
483
        $expiry = null,
484
        $path = '/',
485
        $domain = null,
486
        $secure = false,
487
        $httponly = false
488
    ) {
489
        if (null === $expiry) {
490
            $expiry = time() + (3600 * 24 * 30);
491
        }
492
493
        $this->cookies->set(
494
            $key,
495
            new ResponseCookie($key, $value, $expiry, $path, $domain, $secure, $httponly)
496
        );
497
498
        return $this;
499
    }
500
501
    /**
502
     * Tell the browser not to cache the response
503
     *
504
     * @return AbstractResponse
505
     */
506
    public function noCache()
507
    {
508
        $this->header('Pragma', 'no-cache');
509
        $this->header('Cache-Control', 'no-store, no-cache');
510
511
        return $this;
512
    }
513
514
    /**
515
     * Redirects the request to another URL
516
     *
517
     * @param string $url   The URL to redirect to
518
     * @param int $code     The HTTP status code to use for redirection
519
     * @return AbstractResponse
520
     */
521
    public function redirect($url, $code = 302)
522
    {
523
        $this->code($code);
524
        $this->header('Location', $url);
525
        $this->lock();
526
527
        return $this;
528
    }
529
}
530