1 <?php
2
3 /*
4 * This file is part of the ICanBoogie package.
5 *
6 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace ICanBoogie\HTTP;
13
14 use ICanBoogie\Accessor\AccessorTrait;
15
16 /**
17 * Class Status
18 *
19 * @package ICanBoogie\HTTP
20 *
21 * @property int $code
22 * @property string $message
23 *
24 * @property-read bool $is_cacheable Whether the status is cacheable.
25 * @property-read bool $is_client_error Whether the status is a client error.
26 * @property-read bool $is_empty Whether the status is empty.
27 * @property-read bool $is_forbidden Whether the status is forbidden.
28 * @property-read bool $is_informational Whether the status is informational.
29 * @property-read bool $is_not_found Whether the status is not found.
30 * @property-read bool $is_ok Whether the status is ok.
31 * @property-read bool $is_redirect Whether the status is a redirection.
32 * @property-read bool $is_server_error Whether the status is a server error.
33 * @property-read bool $is_successful Whether the status is successful.
34 * @property-read bool $is_valid Whether the status is valid.
35 */
36 class Status
37 {
38 use AccessorTrait;
39
40 /**
41 * HTTP status codes and messages.
42 *
43 * @var array
44 */
45 static public $codes_and_messages = [
46
47 100 => "Continue",
48 101 => "Switching Protocols",
49
50 200 => "OK",
51 201 => "Created",
52 202 => "Accepted",
53 203 => "Non-Authoritative Information",
54 204 => "No Content",
55 205 => "Reset Content",
56 206 => "Partial Content",
57
58 300 => "Multiple Choices",
59 301 => "Moved Permanently",
60 302 => "Found",
61 303 => "See Other",
62 304 => "Not Modified",
63 305 => "Use Proxy",
64 307 => "Temporary Redirect",
65
66 400 => "Bad Request",
67 401 => "Unauthorized",
68 402 => "Payment Required",
69 403 => "Forbidden",
70 404 => "Not Found",
71 405 => "Method Not Allowed",
72 406 => "Not Acceptable",
73 407 => "Proxy Authentication Required",
74 408 => "Request Timeout",
75 409 => "Conflict",
76 410 => "Gone",
77 411 => "Length Required",
78 412 => "Precondition Failed",
79 413 => "Request Entity Too Large",
80 414 => "Request-URI Too Long",
81 415 => "Unsupported Media Type",
82 416 => "Requested Range Not Satisfiable",
83 417 => "Expectation Failed",
84 418 => "I'm a teapot",
85
86 500 => "Internal Server Error",
87 501 => "Not Implemented",
88 502 => "Bad Gateway",
89 503 => "Service Unavailable",
90 504 => "Gateway Timeout",
91 505 => "HTTP Version Not Supported"
92
93 ];
94
95 /**
96 * Creates a new instance from the provided status.
97 *
98 * @param $status
99 *
100 * @return Status
101 *
102 * @throws \InvalidArgumentException When the HTTP status code is not valid.
103 */
104 static public function from($status)
105 {
106 if ($status instanceof self)
107 {
108 return $status;
109 }
110
111 $message = null;
112
113 if (is_array($status))
114 {
115 list($code, $message) = $status;
116 }
117 elseif (is_numeric($status))
118 {
119 $code = (int) $status;
120 }
121 else
122 {
123 if (!preg_match('/^(\d{3})\s+(.+)$/', $status, $matches))
124 {
125 throw new \InvalidArgumentException("Invalid status: $status.");
126 }
127
128 list(, $code, $message) = $matches;
129 }
130
131 return new static($code, $message);
132 }
133
134 /**
135 * Asserts that a status code is valid.
136 *
137 * @param $code
138 *
139 * @throws StatusCodeNotValid if the status code is not valid.
140 */
141 static private function assert_code_is_valid($code)
142 {
143 if ($code >= 100 && $code < 600)
144 {
145 return;
146 }
147
148 throw new StatusCodeNotValid($code);
149 }
150
151 /**
152 * Status code.
153 *
154 * @var int
155 */
156 private $code;
157
158 protected function set_code($code)
159 {
160 self::assert_code_is_valid($code);
161
162 $this->code = $code;
163 }
164
165 protected function get_code()
166 {
167 return $this->code;
168 }
169
170 /**
171 * Whether the status is valid.
172 *
173 * A status is considered valid when its code is between 100 and 600, 100 included.
174 *
175 * @return bool
176 */
177 protected function get_is_valid()
178 {
179 return $this->code >= 100 && $this->code < 600;
180 }
181
182 /**
183 * Whether the status is informational.
184 *
185 * A status is considered informational when its code is between 100 and 200, 100 included.
186 *
187 * @return bool
188 */
189 protected function get_is_informational()
190 {
191 return $this->code >= 100 && $this->code < 200;
192 }
193
194 /**
195 * Whether the status is successful.
196 *
197 * A status is considered successful when its code is between 200 and 300, 200 included.
198 *
199 * @return bool
200 */
201 protected function get_is_successful()
202 {
203 return $this->code >= 200 && $this->code < 300;
204 }
205
206 /**
207 * Whether the status is a redirection.
208 *
209 * A status is considered to be a redirection when its code is between 300 and 400, 300
210 * included.
211 *
212 * @return bool
213 */
214 protected function get_is_redirect()
215 {
216 return $this->code >= 300 && $this->code < 400;
217 }
218
219 /**
220 * Whether the status is a client error.
221 *
222 * A status is considered a client error when its code is between 400 and 500, 400
223 * included.
224 *
225 * @return bool
226 */
227 protected function get_is_client_error()
228 {
229 return $this->code >= 400 && $this->code < 500;
230 }
231
232 /**
233 * Whether the status is a server error.
234 *
235 * A status is considered a server error when its code is between 500 and 600, 500
236 * included.
237 *
238 * @return bool
239 */
240 protected function get_is_server_error()
241 {
242 return $this->code >= 500 && $this->code < 600;
243 }
244
245 /**
246 * Whether the status is ok.
247 *
248 * A status is considered ok when its code is 200.
249 *
250 * @return bool
251 */
252 protected function get_is_ok()
253 {
254 return $this->code == 200;
255 }
256
257 /**
258 * Whether the status is forbidden.
259 *
260 * A status is considered forbidden ok when its code is 403.
261 *
262 * @return bool
263 */
264 protected function get_is_forbidden()
265 {
266 return $this->code == 403;
267 }
268
269 /**
270 * Whether the status is not found.
271 *
272 * A status is considered not found when its code is 404.
273 *
274 * @return bool
275 */
276 protected function get_is_not_found()
277 {
278 return $this->code == 404;
279 }
280
281 /**
282 * Whether the status is empty.
283 *
284 * A status is considered empty when its code is 201, 204 or 304.
285 *
286 * @return bool
287 */
288 protected function get_is_empty()
289 {
290 static $range = [ 201, 204, 304 ];
291
292 return in_array($this->code, $range);
293 }
294
295 /**
296 * Whether the status is cacheable.
297 *
298 * @return bool
299 */
300 protected function get_is_cacheable()
301 {
302 static $range = [ 200, 203, 300, 301, 302, 404, 410 ];
303
304 return in_array($this->code, $range);
305 }
306
307 /**
308 * Message describing the status code.
309 *
310 * @var string
311 */
312 private $message;
313
314 protected function set_message($message)
315 {
316 $this->message = $message;
317 }
318
319 protected function get_message()
320 {
321 $message = $this->message;
322 $code = $this->code;
323
324 if (!$message && $code)
325 {
326 $message = self::$codes_and_messages[$code];
327 }
328
329 return $message;
330 }
331
332 /**
333 * @param int $code
334 * @param string|null $message
335 */
336 public function __construct($code = 200, $message = null)
337 {
338 self::assert_code_is_valid($code);
339
340 $this->code = $code;
341 $this->message = $message ?: self::$codes_and_messages[$code];
342 }
343
344 public function __toString()
345 {
346 return "$this->code " . $this->get_message();
347 }
348 }
349