Response   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 451
Duplicated Lines 0 %

Test Coverage

Coverage 18.25%

Importance

Changes 0
Metric Value
eloc 78
dl 0
loc 451
ccs 23
cts 126
cp 0.1825
rs 8.64
c 0
b 0
f 0
wmc 47

31 Methods

Rating   Name   Duplication   Size   Complexity  
A hasState() 0 3 1
A getHeaders() 0 3 1
A getStatusCode() 0 3 1
A setHeaders() 0 3 1
A getHeadersAsString() 0 9 2
A setException() 0 3 1
A setStatusCode() 0 7 1
A setStatusReasonPhrase() 0 3 1
A redirect() 0 4 1
A hasException() 0 3 1
A setState() 0 3 1
A setBodyStream() 0 3 1
A init() 0 22 1
A removeHeader() 0 4 2
A resetBodyStream() 0 3 1
A getHeader() 0 6 2
A getException() 0 3 1
A getBodyStream() 0 3 1
A appendBodyStream() 0 3 1
B copyBodyStream() 0 21 8
A initDefaultHeaders() 0 9 1
A addHeader() 0 19 4
A getCookie() 0 6 2
A hasHeader() 0 3 1
A getState() 0 3 1
A getVersion() 0 3 1
A getBodyContent() 0 3 1
A getStatusReasonPhrase() 0 3 1
A addCookie() 0 18 3
A getCookies() 0 3 1
A hasCookie() 0 3 1

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.

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
3
/**
4
 * AppserverIo\Appserver\ServletEngine\Http\Response
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2015 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/appserver
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Appserver\ServletEngine\Http;
22
23
use AppserverIo\Http\HttpProtocol;
24
use AppserverIo\Http\HttpException;
25
use AppserverIo\Http\HttpResponseStates;
26
use AppserverIo\Psr\HttpMessage\CookieInterface;
27
use AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface;
28
29
/**
30
 * A servlet response implementation.
31
 *
32
 * @author    Tim Wagner <[email protected]>
33
 * @copyright 2015 TechDivision GmbH <[email protected]>
34
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
35
 * @link      https://github.com/appserver-io/appserver
36
 * @link      http://www.appserver.io
37
 *
38
 * @property resource                                       $bodyStream         The body content stream resource
39
 * @property \AppserverIo\Psr\HttpMessage\CookieInterface[] $cookies            The cookie instance to add
40
 * @property \Exception                                     $exception          The exception to bind.
41
 * @property string[]                                       $headers            The headers array
42
 * @property string                                         $mimeType           The responses mime type
43
 * @property integer                                        $state              The state value
44
 * @property integer                                        $statusCode         The status code to set
45
 * @property string                                         $statusReasonPhrase The reason phrase
46
 * @property string                                         $version            The HTTP version string to return
47
 */
48
class Response implements HttpServletResponseInterface
49
{
50
51
    /**
52
     * Initialises the response object to default properties.
53
     *
54
     * @return void
55
     */
56
    public function init()
57
    {
58
59
        // init body stream
60
        $this->bodyStream = '';
0 ignored issues
show
Documentation Bug introduced by
It seems like '' of type string is incompatible with the declared type resource of property $bodyStream.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61
62
        // init exception
63
        $this->exception = null;
64
65
        // init default response properties
66
        $this->statusCode = 200;
67
        $this->version = 'HTTP/1.1';
68
        $this->statusReasonPhrase = "OK";
69
        $this->mimeType = "text/plain";
70
        $this->state = HttpResponseStates::INITIAL;
71
72
        // initialize cookies and headers
73
        $this->cookies = array();
74
        $this->headers = array();
75
76
        // initialize the default headers
77
        $this->initDefaultHeaders();
78
    }
79
80
    /**
81
     * Initializes the response with the default headers.
82
     *
83
     * @return void
84
     */
85
    protected function initDefaultHeaders()
86
    {
87
        // add this header to prevent .php request to be cached
88
        $this->addHeader(HttpProtocol::HEADER_EXPIRES, '19 Nov 1981 08:52:00 GMT');
89
        $this->addHeader(HttpProtocol::HEADER_CACHE_CONTROL, 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
90
        $this->addHeader(HttpProtocol::HEADER_PRAGMA, 'no-cache');
91
92
        // set per default text/html mimetype
93
        $this->addHeader(HttpProtocol::HEADER_CONTENT_TYPE, 'text/html');
94
    }
95
96
    /**
97
     * Adds a cookie.
98
     *
99
     * @param \AppserverIo\Psr\HttpMessage\CookieInterface $cookie The cookie instance to add
100
     *
101
     * @return void
102
     */
103
    public function addCookie(CookieInterface $cookie)
104
    {
105
106
        // check if this cookie has already been set
107
        if ($this->hasCookie($name = $cookie->getName())) {
108
            // then check if we've already one cookie header available
109
            if (is_array($cookieValue = $this->getCookie($name))) {
0 ignored issues
show
introduced by
The condition is_array($cookieValue = $this->getCookie($name)) is always false.
Loading history...
110
                $cookieValue[] = $cookie;
111
            } else {
112
                $cookieValue = array($cookieValue, $cookie);
113
            }
114
115
            // add the cookie array
116
            $this->cookies[$name] = $cookieValue;
117
118
        } else {
119
            // when add it the first time, simply add it
120
            $this->cookies[$name] = $cookie;
121
        }
122
    }
123
124
    /**
125
     * Returns TRUE if the response already has a cookie with the passed
126
     * name, else FALSE.
127
     *
128
     * @param string $cookieName Name of the cookie to be checked
129
     *
130
     * @return boolean TRUE if the response already has the cookie, else FALSE
131
     */
132
    public function hasCookie($cookieName)
133
    {
134
        return isset($this->cookies[$cookieName]);
135
    }
136
137
    /**
138
     * Returns the cookie with the a cookie
139
     *
140
     * @param string $cookieName Name of the cookie to be checked
141
     *
142
     * @return mixed The cookie instance or an array of cookie instances
143
     * @throws \AppserverIo\Http\HttpException Is thrown if the requested header is not available
144
     */
145
    public function getCookie($cookieName)
146
    {
147
        if ($this->hasCookie($cookieName) === false) {
148
            throw new HttpException("Cookie '$cookieName' not found");
149
        }
150
        return $this->cookies[$cookieName];
151
    }
152
153
    /**
154
     * Returns the cookies.
155
     *
156
     * @return array The cookies
157
     */
158
    public function getCookies()
159
    {
160
        return $this->cookies;
161
    }
162
163
    /**
164
     * Return content
165
     *
166
     * @return string $content
167
     */
168
    public function getBodyContent()
169
    {
170
        return $this->bodyStream;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->bodyStream returns the type resource which is incompatible with the documented return type string.
Loading history...
171
    }
172
173
    /**
174
     * Reset the body stream
175
     *
176
     * @return void
177
     */
178
    public function resetBodyStream()
179
    {
180
        $this->bodyStream = '';
0 ignored issues
show
Documentation Bug introduced by
It seems like '' of type string is incompatible with the declared type resource of property $bodyStream.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
181
    }
182
183
    /**
184
     * Returns the body stream as a resource.
185
     *
186
     * @return resource The body stream
187
     */
188
    public function getBodyStream()
189
    {
190
        return $this->bodyStream;
191
    }
192
193
    /**
194
     * Appends the content.
195
     *
196
     * @param string $content The content to append
197
     *
198
     * @return void
199
     */
200
    public function appendBodyStream($content)
201
    {
202
        $this->bodyStream .= $content;
203
    }
204
205
    /**
206
     * Copies a source stream to body stream.
207
     *
208
     * @param resource $sourceStream The file pointer to source stream
209
     * @param integer  $maxlength    The max length to read from source stream
210
     * @param integer  $offset       The offset from source stream to read
211
     *
212
     * @return integer The total number of bytes copied
213
     */
214
    public function copyBodyStream($sourceStream, $maxlength = null, $offset = 0)
215
    {
216
217
        // check if a stream has been passed
218
        if (is_resource($sourceStream)) {
219
            if ($offset && $maxlength) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxlength of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
220
                $this->bodyStream = stream_get_contents($sourceStream, $maxlength, $offset);
0 ignored issues
show
Documentation Bug introduced by
It seems like stream_get_contents($sou...m, $maxlength, $offset) of type string is incompatible with the declared type resource of property $bodyStream.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
221
            }
222
            if (!$offset && $maxlength) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxlength of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
223
                $this->bodyStream = stream_get_contents($sourceStream, $maxlength);
224
            }
225
            if (!$offset && !$maxlength) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxlength of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
226
                $this->bodyStream = stream_get_contents($sourceStream);
227
            }
228
        } else {
229
            // if not, copy the string
230
            $this->bodyStream = substr($sourceStream, $offset, $maxlength);
231
        }
232
233
        // return the string length
234
        return strlen($this->bodyStream);
0 ignored issues
show
Bug introduced by
$this->bodyStream of type resource is incompatible with the type string expected by parameter $string of strlen(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
        return strlen(/** @scrutinizer ignore-type */ $this->bodyStream);
Loading history...
235
    }
236
237
    /**
238
     * Resetss the stream resource pointing to body content.
239
     *
240
     * @param resource $bodyStream The body content stream resource
241
     *
242
     * @return void
243
     */
244
    public function setBodyStream($bodyStream)
245
    {
246
        $this->copyBodyStream($bodyStream);
247
    }
248
249
    /**
250
     * Resets all headers by given array
251
     *
252
     * @param array $headers The headers array
253
     *
254
     * @return void
255
     */
256
    public function setHeaders(array $headers)
257
    {
258
        $this->headers = $headers;
259
    }
260
261
    /**
262
     * Returns all headers as array
263
     *
264
     * @return array
265
     */
266
    public function getHeaders()
267
    {
268
        return $this->headers;
269
    }
270
271
    /**
272
     * Returns the headers as string
273
     *
274
     * @return string
275
     */
276
    public function getHeadersAsString()
277
    {
278
        $headerString = '';
279
        foreach ($this->getHeaders() as $name => $header) {
280
            // build up a header string
281
            $headerString .= $name . ': ' . $header . PHP_EOL;
282
        }
283
284
        return $headerString;
285
    }
286
287
    /**
288
     * Adds a header information got from connection. We've to take care that headers
289
     * like Set-Cookie header can exist multiple times. To support this create an
290
     * array that keeps the multiple header values.
291
     *
292
     * @param string  $name   The header name
293
     * @param string  $value  The headers value
294
     * @param boolean $append If TRUE and a header with the passed name already exists, the value will be appended
295
     *
296
     * @return void
297
     */
298 2
    public function addHeader($name, $value, $append = false)
299
    {
300
        // normalize header names in case of 'Content-type' into 'Content-Type'
301 2
        $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
302
303
        // check if we've a Set-Cookie header to process
304 2
        if ($this->hasHeader($name) && $append === true) {
305
            // then check if we've already one cookie header available
306
            if (is_array($headerValue = $this->getHeader($name))) {
0 ignored issues
show
introduced by
The condition is_array($headerValue = $this->getHeader($name)) is always false.
Loading history...
307
                $headerValue[] = $value;
308
            } else {
309
                $headerValue = array($headerValue, $value);
310
            }
311
312
            // if no cookie header simple add it
313
            $this->headers[$name] = $headerValue;
314
315
        } else {
316 2
            $this->headers[$name] = $value;
317
        }
318 2
    }
319
320
    /**
321
     * Returns header by given name.
322
     *
323
     * @param string $name The header name to get
324
     *
325
     * @return mixed Usually a string, but can also be an array if we request the Set-Cookie header
326
     * @throws \AppserverIo\Http\HttpException Is thrown if the requested header is not available
327
     */
328 2
    public function getHeader($name)
329
    {
330 2
        if (isset($this->headers[$name]) === false) {
331
            throw new HttpException("Response header '$name' not found");
332
        }
333 2
        return $this->headers[$name];
334
    }
335
336
    /**
337
     * Returns the http version of the response
338
     *
339
     * @return string
340
     */
341
    public function getVersion()
342
    {
343
        return $this->version;
344
    }
345
346
    /**
347
     * Removes the header with the passed name.
348
     *
349
     * @param string $name Name of the header to remove
350
     *
351
     * @return void
352
     */
353
    public function removeHeader($name)
354
    {
355
        if (isset($this->headers[$name])) {
356
            unset($this->headers[$name]);
357
        }
358
    }
359
360
    /**
361
     * Check's if header exists by given name
362
     *
363
     * @param string $name The header name to check
364
     *
365
     * @return boolean
366
     */
367 2
    public function hasHeader($name)
368
    {
369 2
        return isset($this->headers[$name]);
370
    }
371
372
    /**
373
     * Sets the http response status code
374
     *
375
     * @param int $code The status code to set
376
     *
377
     * @return void
378
     */
379 2
    public function setStatusCode($code)
380
    {
381
        // set status code
382 2
        $this->statusCode = $code;
383
384
        // lookup reason phrase by code and set
385 2
        $this->setStatusReasonPhrase(HttpProtocol::getStatusReasonPhraseByCode($code));
386 2
    }
387
388
    /**
389
     * Returns the response status code
390
     *
391
     * @return int
392
     */
393 2
    public function getStatusCode()
394
    {
395 2
        return $this->statusCode;
396
    }
397
398
    /**
399
     * Sets the status reason phrase
400
     *
401
     * @param string $statusReasonPhrase The reason phrase
402
     *
403
     * @return void
404
     */
405 2
    public function setStatusReasonPhrase($statusReasonPhrase)
406
    {
407 2
        $this->statusReasonPhrase = $statusReasonPhrase;
408 2
    }
409
410
    /**
411
     * Returns the status phrase based on the status code
412
     *
413
     * @return string
414
     */
415
    public function getStatusReasonPhrase()
416
    {
417
        return $this->statusReasonPhrase;
418
    }
419
420
    /**
421
     * Sets state of response
422
     *
423
     * @param int $state The state value
424
     *
425
     * @return void
426
     */
427
    public function setState($state)
428
    {
429
        $this->state = $state;
430
    }
431
432
    /**
433
     * Returns the current state
434
     *
435
     * @return int
436
     */
437
    public function getState()
438
    {
439
        return $this->state;
440
    }
441
442
    /**
443
     * Compares current state with given state
444
     *
445
     * @param int $state The state to compare with
446
     *
447
     * @return bool Whether state is equal (true) or not (false)
448
     */
449
    public function hasState($state)
450
    {
451
        return ($this->state === $state);
452
    }
453
454
    /**
455
     * Redirects to the passed URL by adding a 'Location' header and
456
     * setting the appropriate status code, by default 301.
457
     *
458
     * @param string  $url  The URL to forward to
459
     * @param integer $code The status code to set
460
     *
461
     * @return void
462
     */
463 2
    public function redirect($url, $code = 301)
464
    {
465 2
        $this->setStatusCode($code);
466 2
        $this->addHeader(HttpProtocol::HEADER_LOCATION, $url);
467 2
    }
468
469
    /**
470
     * Queries whether the response contains an exception or not.
471
     *
472
     * @return boolean TRUE if an exception has been attached, else FALSE
473
     */
474
    public function hasException()
475
    {
476
        return $this->exception instanceof \Exception;
477
    }
478
479
    /**
480
     * Returns the exception bound to the response.
481
     *
482
     * @return \Exception|null The exception
483
     */
484
    public function getException()
485
    {
486
        return $this->exception;
487
    }
488
489
    /**
490
     * Binds the exception to the response.
491
     *
492
     * @param \Exception $exception The exception to bind.
493
     *
494
     * @return void
495
     */
496
    public function setException(\Exception $exception)
497
    {
498
        $this->exception = $exception;
499
    }
500
}
501