Completed
Push — master ( 4fd434...e1a522 )
by Bohuslav
03:35
created

Response   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 12
c 4
b 2
f 0
lcom 1
cbo 1
dl 0
loc 191
ccs 31
cts 31
cp 1
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getStatusCode() 0 4 1
A withStatus() 0 16 2
A getReasonPhrase() 0 12 3
B validateStatus() 0 12 5
1
<?php
2
namespace Kambo\Http\Message;
3
4
// \Spl
5
use \InvalidArgumentException;
6
7
// \Psr
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
11
// \Http\Message
12
use Kambo\Http\Message\Message;
13
use Kambo\Http\Message\Headers;
14
use Kambo\Http\Message\Stream;
15
16
/**
17
 * Representation of an outgoing, server-side response.
18
 *
19
 * Per the HTTP specification, this class encapsulate properties for
20
 * each of the following:
21
 *
22
 * - Protocol version
23
 * - Status code and reason phrase
24
 * - Headers
25
 * - Message body
26
 *
27
 * Responses are considered immutable; all methods that change state retain 
28
 * the internal state of the current message and return an instance that 
29
 * contains the changed state.
30
 *
31
 * @package Kambo\Http\Message
32
 * @author  Bohuslav Simek <[email protected]>
33
 * @license MIT
34
 */
35
class Response extends Message implements ResponseInterface
36
{
37
    private $status       = 200;
38
    private $reasonPhrase = '';
39
40
    /**
41
     * Status codes and reason phrases
42
     *
43
     * @var array
44
     */
45
    private $messages = [
46
        //Informational 1xx
47
        100 => 'Continue',
48
        101 => 'Switching Protocols',
49
        102 => 'Processing',
50
        //Successful 2xx
51
        200 => 'OK',
52
        201 => 'Created',
53
        202 => 'Accepted',
54
        203 => 'Non-Authoritative Information',
55
        204 => 'No Content',
56
        205 => 'Reset Content',
57
        206 => 'Partial Content',
58
        207 => 'Multi-Status',
59
        208 => 'Already Reported',
60
        226 => 'IM Used',
61
        //Redirection 3xx
62
        300 => 'Multiple Choices',
63
        301 => 'Moved Permanently',
64
        302 => 'Found',
65
        303 => 'See Other',
66
        304 => 'Not Modified',
67
        305 => 'Use Proxy',
68
        306 => '(Unused)',
69
        307 => 'Temporary Redirect',
70
        308 => 'Permanent Redirect',
71
        //Client Error 4xx
72
        400 => 'Bad Request',
73
        401 => 'Unauthorized',
74
        402 => 'Payment Required',
75
        403 => 'Forbidden',
76
        404 => 'Not Found',
77
        405 => 'Method Not Allowed',
78
        406 => 'Not Acceptable',
79
        407 => 'Proxy Authentication Required',
80
        408 => 'Request Timeout',
81
        409 => 'Conflict',
82
        410 => 'Gone',
83
        411 => 'Length Required',
84
        412 => 'Precondition Failed',
85
        413 => 'Request Entity Too Large',
86
        414 => 'Request-URI Too Long',
87
        415 => 'Unsupported Media Type',
88
        416 => 'Requested Range Not Satisfiable',
89
        417 => 'Expectation Failed',
90
        418 => 'I\'m a teapot',
91
        422 => 'Unprocessable Entity',
92
        423 => 'Locked',
93
        424 => 'Failed Dependency',
94
        426 => 'Upgrade Required',
95
        428 => 'Precondition Required',
96
        429 => 'Too Many Requests',
97
        431 => 'Request Header Fields Too Large',
98
        451 => 'Unavailable For Legal Reasons',
99
        //Server Error 5xx
100
        500 => 'Internal Server Error',
101
        501 => 'Not Implemented',
102
        502 => 'Bad Gateway',
103
        503 => 'Service Unavailable',
104
        504 => 'Gateway Timeout',
105
        505 => 'HTTP Version Not Supported',
106
        506 => 'Variant Also Negotiates',
107
        507 => 'Insufficient Storage',
108
        508 => 'Loop Detected',
109
        510 => 'Not Extended',
110
        511 => 'Network Authentication Required',
111
    ];
112
113
    /**
114
     * Create new outgoing, server-side response.
115
     *
116
     * @param int                   $status  The response status code.
117
     * @param Headers|null          $headers The response headers.
118
     * @param StreamInterface|null  $body    The response body.
119
     */
120 7
    public function __construct($status = 200, Headers $headers = null, StreamInterface $body = null)
121
    {
122 7
        parent::__construct($headers, $body);
123 7
        $this->validateStatus($status);
124
125 7
        $this->status = $status;
126 7
    }
127
128
    /**
129
     * Gets the response status code.
130
     *
131
     * The status code is a 3-digit integer result code of the server's attempt
132
     * to understand and satisfy the request.
133
     *
134
     * @return int Status code.
135
     */
136 3
    public function getStatusCode()
137
    {
138 3
        return $this->status;
139
    }
140
141
    /**
142
     * Return an instance with the specified status code and, optionally, reason phrase.
143
     *
144
     * If no reason phrase is specified, the RFC 7231 or IANA recommended reason 
145
     * phrase is returned according response status code.
146
     *
147
     * This method retain the immutability of the message, and return an 
148
     * instance that has the updated status and reason phrase.
149
     *
150
     * @link http://tools.ietf.org/html/rfc7231#section-6
151
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
152
     *
153
     * @param int    $code         The 3-digit integer result code to set.
154
     * @param string $reasonPhrase The reason phrase to use with the provided status code;
155
     *                             if none is provided, implementations use the defaults
156
     *                             as suggested in the HTTP specification.
157
     *
158
     * @return self
159
     *
160
     * @throws \InvalidArgumentException For invalid status code arguments.
161
     */
162 4
    public function withStatus($code, $reasonPhrase = '')
163
    {
164 4
        $clone = clone $this;
165
166 4
        $this->validateStatus($code);
167
168 3
        $clone->status = $code;
169
170 3
        if ($reasonPhrase === '') {
171 1
            $reasonPhrase = $this->messages[$code];
172 1
        }
173
174 3
        $clone->reasonPhrase = $reasonPhrase;
175
176 3
        return $clone;
177
    }
178
179
    /**
180
     * Gets the response reason phrase associated with the status code.
181
     *
182
     * The default RFC 7231 recommended reason phrase is selected according 
183
     * response's status code.
184
     *
185
     * @link http://tools.ietf.org/html/rfc7231#section-6
186
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
187
     *
188
     * @return string Reason phrase; return an empty string if none present.
189
     */
190 3
    public function getReasonPhrase()
191
    {
192 3
        if ($this->reasonPhrase) {
193 1
            return $this->reasonPhrase;
194
        }
195
196 2
        if (isset($this->messages[$this->status])) {
197 1
            return $this->messages[$this->status];
198
        }
199
200 1
        return '';
201
    }
202
203
    // ------------ PRIVATE METHODS
204
205
    /**
206
     * Validate status code
207
     *
208
     * @param int $code The 3-digit integer result code to set.
209
     *
210
     * @return void
211
     * @throws \InvalidArgumentException For invalid status code.
212
     */
213 7
    private function validateStatus($code)
214
    {
215 7
        if (!is_numeric($code)
216 7
            || is_float($code)
217 7
            || $code < 100
218 7
            || $code >= 600
219 7
        ) {
220 1
            throw new InvalidArgumentException(
221
                'Status code must be an integer between 100 and 599, inclusive'
222 1
            );
223
        }
224 7
    }
225
}
226