1: <?php
2:
3: namespace Alpha\Util\Http;
4:
5: use Alpha\Exception\IllegalArguementException;
6: use Alpha\Util\Helper\Validator;
7:
8: /**
9: * A class to encapsulate a HTTP Response.
10: *
11: * @since 2.0
12: *
13: * @author John Collins <dev@alphaframework.org>
14: * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
15: * @copyright Copyright (c) 2015, John Collins (founder of Alpha Framework).
16: * All rights reserved.
17: *
18: * <pre>
19: * Redistribution and use in source and binary forms, with or
20: * without modification, are permitted provided that the
21: * following conditions are met:
22: *
23: * * Redistributions of source code must retain the above
24: * copyright notice, this list of conditions and the
25: * following disclaimer.
26: * * Redistributions in binary form must reproduce the above
27: * copyright notice, this list of conditions and the
28: * following disclaimer in the documentation and/or other
29: * materials provided with the distribution.
30: * * Neither the name of the Alpha Framework nor the names
31: * of its contributors may be used to endorse or promote
32: * products derived from this software without specific
33: * prior written permission.
34: *
35: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
36: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
37: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
40: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
42: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
43: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
44: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
46: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48: * </pre>
49: */
50: class Response
51: {
52: /**
53: * The body of the response.
54: *
55: * @var string
56: *
57: * @since 2.0
58: */
59: private $body;
60:
61: /**
62: * The status code of the response.
63: *
64: * @var int
65: *
66: * @since 2.0
67: */
68: private $status;
69:
70: /**
71: * Array of supported HTTP response codes.
72: *
73: * @var array
74: *
75: * @since 2.0
76: */
77: private $HTTPStatusCodes = array(
78: 100 => 'Continue',
79: 101 => 'Switching Protocols',
80: 102 => 'Processing',
81: 200 => 'OK',
82: 201 => 'Created',
83: 202 => 'Accepted',
84: 203 => 'Non-Authoritative Information',
85: 204 => 'No Content',
86: 205 => 'Reset Content',
87: 206 => 'Partial Content',
88: 207 => 'Multi-Status',
89: 300 => 'Multiple Choices',
90: 301 => 'Moved Permanently',
91: 302 => 'Found',
92: 303 => 'See Other',
93: 304 => 'Not Modified',
94: 305 => 'Use Proxy',
95: 306 => 'Switch Proxy',
96: 307 => 'Temporary Redirect',
97: 400 => 'Bad Request',
98: 401 => 'Unauthorized',
99: 402 => 'Payment Required',
100: 403 => 'Forbidden',
101: 404 => 'Not Found',
102: 405 => 'Method Not Allowed',
103: 406 => 'Not Acceptable',
104: 407 => 'Proxy Authentication Required',
105: 408 => 'Request Timeout',
106: 409 => 'Conflict',
107: 410 => 'Gone',
108: 411 => 'Length Required',
109: 412 => 'Precondition Failed',
110: 413 => 'Request Entity Too Large',
111: 414 => 'Request-URI Too Long',
112: 415 => 'Unsupported Media Type',
113: 416 => 'Requested Range Not Satisfiable',
114: 417 => 'Expectation Failed',
115: 418 => 'I\'m a teapot',
116: 422 => 'Unprocessable Entity',
117: 423 => 'Locked',
118: 424 => 'Failed Dependency',
119: 425 => 'Unordered Collection',
120: 426 => 'Upgrade Required',
121: 449 => 'Retry With',
122: 450 => 'Blocked by Windows Parental Controls',
123: 500 => 'Internal Server Error',
124: 501 => 'Not Implemented',
125: 502 => 'Bad Gateway',
126: 503 => 'Service Unavailable',
127: 504 => 'Gateway Timeout',
128: 505 => 'HTTP Version Not Supported',
129: 506 => 'Variant Also Negotiates',
130: 507 => 'Insufficient Storage',
131: 509 => 'Bandwidth Limit Exceeded',
132: 510 => 'Not Extended',
133: );
134:
135: /**
136: * An associative array of headers for the response.
137: *
138: * @var array
139: *
140: * @since 2.0
141: */
142: private $headers;
143:
144: /**
145: * An associative array of HTTP cookies on this response.
146: *
147: * @var array
148: *
149: * @since 2.0
150: */
151: private $cookies;
152:
153: /**
154: * Build the response.
155: *
156: * @param int $status The HTTP status code of the response.
157: * @param string $body The body of the response (optional).
158: * @param array $headers The headers to set on the response (optional).
159: *
160: * @throws Alpha\Exception\IllegalArguementException
161: */
162: public function __construct($status, $body = null, $headers = array())
163: {
164: $this->headers = $headers;
165:
166: if (isset($body)) {
167: $this->body = $body;
168: }
169:
170: if (array_key_exists($status, $this->HTTPStatusCodes)) {
171: $this->status = $status;
172: } else {
173: throw new IllegalArguementException('The status code provided ['.$status.'] is invalid');
174: }
175: }
176:
177: /**
178: * Get the response body.
179: *
180: * @return string|null
181: *
182: * @since 2.0
183: */
184: public function getBody()
185: {
186: return $this->body;
187: }
188:
189: /**
190: * Set the response body.
191: *
192: * @param string $body The response body.
193: *
194: * @since 2.0
195: */
196: public function setBody($body)
197: {
198: $this->body = $body;
199: }
200:
201: /**
202: * Get the status code of the response.
203: *
204: * @return int
205: *
206: * @since 2.0
207: */
208: public function getStatus()
209: {
210: return $this->status;
211: }
212:
213: /**
214: * Get the status message of the response.
215: *
216: * @return string
217: */
218: public function getStatusMessage()
219: {
220: return $this->HTTPStatusCodes[$this->status];
221: }
222:
223: /**
224: * Set the status code of the response.
225: *
226: * @param int $status The response code.
227: *
228: * @throws Alpha\Exception\IllegalArguementException
229: *
230: * @since 2.0
231: */
232: public function setStatus($status)
233: {
234: if (array_key_exists($status, $this->HTTPStatusCodes)) {
235: $this->status = $status;
236: } else {
237: throw new IllegalArguementException('The status code provided ['.$status.'] is invalid');
238: }
239: }
240:
241: /**
242: * Set a header key/value tuple for the response.
243: *
244: * @param string $header The header key name.
245: * @param string $value The header value.
246: *
247: * @since 2.0
248: */
249: public function setHeader($header, $value)
250: {
251: $this->headers[$header] = $value;
252: }
253:
254: /**
255: * Get all of the headers for the response.
256: *
257: * @return array
258: *
259: * @since 2.0
260: */
261: public function getHeaders()
262: {
263: return $this->headers;
264: }
265:
266: /**
267: * Get the header matching the key provided.
268: *
269: * @param string $key The key to search for
270: * @param mixed $default If key is not found, return this instead
271: *
272: * @return mixed
273: *
274: * @since 2.0
275: */
276: public function getHeader($key, $default = null)
277: {
278: if (array_key_exists($key, $this->headers)) {
279: return $this->headers[$key];
280: } else {
281: return $default;
282: }
283: }
284:
285: /**
286: * Set a cookie key/value tuple for the response.
287: *
288: * @param string $cookie The cookie key name.
289: * @param string $value The cookie value.
290: *
291: * @since 2.0
292: */
293: public function setCookie($cookie, $value)
294: {
295: $this->cookies[$cookie] = $value;
296: }
297:
298: /**
299: * Get all of the cookies for the response.
300: *
301: * @return array
302: *
303: * @since 2.0
304: */
305: public function getCookies()
306: {
307: return $this->cookies;
308: }
309:
310: /**
311: * Get the cookie matching the key provided.
312: *
313: * @param string $key The key to search for
314: * @param mixed $default If key is not found, return this instead
315: *
316: * @return mixed
317: *
318: * @since 2.0
319: */
320: public function getCookie($key, $default = null)
321: {
322: if (array_key_exists($key, $this->cookies)) {
323: return $this->cookies[$key];
324: } else {
325: return $default;
326: }
327: }
328:
329: /**
330: * Get the content length of the response.
331: *
332: * @return int
333: *
334: * @since 2.0
335: */
336: public function getContentLength()
337: {
338: return strlen($this->body);
339: }
340:
341: /**
342: * Builds a redirect response.
343: *
344: * @param string $URL The URL to redirect the client to.
345: *
346: * @throws Alpha\Exception\IllegalArguementException
347: *
348: * @since 2.0
349: */
350: public function redirect($URL)
351: {
352: if (Validator::isURL($URL)) {
353: $this->headers = array();
354: $this->setHeader('Location', $URL);
355: } else {
356: throw new IllegalArguementException('Unable to redirect to URL ['.$URL.'] as it is invalid');
357: }
358: }
359:
360: /**
361: * Sends the current response to standard output before exiting the current process.
362: *
363: * @since 2.0
364: */
365: public function send()
366: {
367: http_response_code($this->status);
368:
369: foreach ($this->headers as $header => $value) {
370: header($header.': '.$value);
371: }
372:
373: header_remove('X-Powered-By');
374:
375: if (isset($this->body)) {
376: header('Content-Length: '.$this->getContentLength());
377: echo $this->body;
378: }
379: }
380: }
381: