Response::withStatus()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

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