1: <?php
2: /**
3: * Slim - a micro PHP 5 framework
4: *
5: * @author Josh Lockhart <info@slimframework.com>
6: * @copyright 2011 Josh Lockhart
7: * @link http://www.slimframework.com
8: * @license http://www.slimframework.com/license
9: * @version 2.2.0
10: * @package Slim
11: *
12: * MIT LICENSE
13: *
14: * Permission is hereby granted, free of charge, to any person obtaining
15: * a copy of this software and associated documentation files (the
16: * "Software"), to deal in the Software without restriction, including
17: * without limitation the rights to use, copy, modify, merge, publish,
18: * distribute, sublicense, and/or sell copies of the Software, and to
19: * permit persons to whom the Software is furnished to do so, subject to
20: * the following conditions:
21: *
22: * The above copyright notice and this permission notice shall be
23: * included in all copies or substantial portions of the Software.
24: *
25: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26: * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28: * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29: * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30: * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31: * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32: */
33: namespace Slim\Http;
34:
35: /**
36: * Response
37: *
38: * This is a simple abstraction over top an HTTP response. This
39: * provides methods to set the HTTP status, the HTTP headers,
40: * and the HTTP body.
41: *
42: * @package Slim
43: * @author Josh Lockhart
44: * @since 1.0.0
45: */
46: class Response implements \ArrayAccess, \Countable, \IteratorAggregate
47: {
48: /**
49: * @var int HTTP status code
50: */
51: protected $status;
52:
53: /**
54: * @var \Slim\Http\Headers List of HTTP response headers
55: */
56: protected $header;
57:
58: /**
59: * @var string HTTP response body
60: */
61: protected $body;
62:
63: /**
64: * @var int Length of HTTP response body
65: */
66: protected $length;
67:
68: /**
69: * @var array HTTP response codes and messages
70: */
71: protected static $messages = array(
72: //Informational 1xx
73: 100 => '100 Continue',
74: 101 => '101 Switching Protocols',
75: //Successful 2xx
76: 200 => '200 OK',
77: 201 => '201 Created',
78: 202 => '202 Accepted',
79: 203 => '203 Non-Authoritative Information',
80: 204 => '204 No Content',
81: 205 => '205 Reset Content',
82: 206 => '206 Partial Content',
83: //Redirection 3xx
84: 300 => '300 Multiple Choices',
85: 301 => '301 Moved Permanently',
86: 302 => '302 Found',
87: 303 => '303 See Other',
88: 304 => '304 Not Modified',
89: 305 => '305 Use Proxy',
90: 306 => '306 (Unused)',
91: 307 => '307 Temporary Redirect',
92: //Client Error 4xx
93: 400 => '400 Bad Request',
94: 401 => '401 Unauthorized',
95: 402 => '402 Payment Required',
96: 403 => '403 Forbidden',
97: 404 => '404 Not Found',
98: 405 => '405 Method Not Allowed',
99: 406 => '406 Not Acceptable',
100: 407 => '407 Proxy Authentication Required',
101: 408 => '408 Request Timeout',
102: 409 => '409 Conflict',
103: 410 => '410 Gone',
104: 411 => '411 Length Required',
105: 412 => '412 Precondition Failed',
106: 413 => '413 Request Entity Too Large',
107: 414 => '414 Request-URI Too Long',
108: 415 => '415 Unsupported Media Type',
109: 416 => '416 Requested Range Not Satisfiable',
110: 417 => '417 Expectation Failed',
111: 422 => '422 Unprocessable Entity',
112: 423 => '423 Locked',
113: //Server Error 5xx
114: 500 => '500 Internal Server Error',
115: 501 => '501 Not Implemented',
116: 502 => '502 Bad Gateway',
117: 503 => '503 Service Unavailable',
118: 504 => '504 Gateway Timeout',
119: 505 => '505 HTTP Version Not Supported'
120: );
121:
122: /**
123: * Constructor
124: * @param string $body The HTTP response body
125: * @param int $status The HTTP response status
126: * @param \Slim\Http\Headers|array $header The HTTP response headers
127: */
128: public function __construct($body = '', $status = 200, $header = array())
129: {
130: $this->status = (int) $status;
131: $headers = array();
132: foreach ($header as $key => $value) {
133: $headers[$key] = $value;
134: }
135: $this->header = new Headers(array_merge(array('Content-Type' => 'text/html'), $headers));
136: $this->body = '';
137: $this->write($body);
138: }
139:
140: /**
141: * Get and set status
142: * @param int|null $status
143: * @return int
144: */
145: public function status($status = null)
146: {
147: if (!is_null($status)) {
148: $this->status = (int) $status;
149: }
150:
151: return $this->status;
152: }
153:
154: /**
155: * Get and set header
156: * @param string $name Header name
157: * @param string|null $value Header value
158: * @return string Header value
159: */
160: public function header($name, $value = null)
161: {
162: if (!is_null($value)) {
163: $this[$name] = $value;
164: }
165:
166: return $this[$name];
167: }
168:
169: /**
170: * Get headers
171: * @return \Slim\Http\Headers
172: */
173: public function headers()
174: {
175: return $this->header;
176: }
177:
178: /**
179: * Get and set body
180: * @param string|null $body Content of HTTP response body
181: * @return string
182: */
183: public function body($body = null)
184: {
185: if (!is_null($body)) {
186: $this->write($body, true);
187: }
188:
189: return $this->body;
190: }
191:
192: /**
193: * Get and set length
194: * @param int|null $length
195: * @return int
196: */
197: public function length($length = null)
198: {
199: if (!is_null($length)) {
200: $this->length = (int) $length;
201: }
202:
203: return $this->length;
204: }
205:
206: /**
207: * Append HTTP response body
208: * @param string $body Content to append to the current HTTP response body
209: * @param bool $replace Overwrite existing response body?
210: * @return string The updated HTTP response body
211: */
212: public function write($body, $replace = false)
213: {
214: if ($replace) {
215: $this->body = $body;
216: } else {
217: $this->body .= (string) $body;
218: }
219: $this->length = strlen($this->body);
220:
221: return $this->body;
222: }
223:
224: /**
225: * Finalize
226: *
227: * This prepares this response and returns an array
228: * of [status, headers, body]. This array is passed to outer middleware
229: * if available or directly to the Slim run method.
230: *
231: * @return array[int status, array headers, string body]
232: */
233: public function finalize()
234: {
235: if (in_array($this->status, array(204, 304))) {
236: unset($this['Content-Type'], $this['Content-Length']);
237:
238: return array($this->status, $this->header, '');
239: } else {
240: return array($this->status, $this->header, $this->body);
241: }
242: }
243:
244: /**
245: * Set cookie
246: *
247: * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
248: * header on its own and delegates this responsibility to the `Slim_Http_Util` class. This
249: * response's header is passed by reference to the utility class and is directly modified. By not
250: * relying on PHP's native implementation, Slim allows middleware the opportunity to massage or
251: * analyze the raw header before the response is ultimately delivered to the HTTP client.
252: *
253: * @param string $name The name of the cookie
254: * @param string|array $value If string, the value of cookie; if array, properties for
255: * cookie including: value, expire, path, domain, secure, httponly
256: */
257: public function setCookie($name, $value)
258: {
259: Util::setCookieHeader($this->header, $name, $value);
260: }
261:
262: /**
263: * Delete cookie
264: *
265: * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
266: * header on its own and delegates this responsibility to the `Slim_Http_Util` class. This
267: * response's header is passed by reference to the utility class and is directly modified. By not
268: * relying on PHP's native implementation, Slim allows middleware the opportunity to massage or
269: * analyze the raw header before the response is ultimately delivered to the HTTP client.
270: *
271: * This method will set a cookie with the given name that has an expiration time in the past; this will
272: * prompt the HTTP client to invalidate and remove the client-side cookie. Optionally, you may
273: * also pass a key/value array as the second argument. If the "domain" key is present in this
274: * array, only the Cookie with the given name AND domain will be removed. The invalidating cookie
275: * sent with this response will adopt all properties of the second argument.
276: *
277: * @param string $name The name of the cookie
278: * @param array $value Properties for cookie including: value, expire, path, domain, secure, httponly
279: */
280: public function deleteCookie($name, $value = array())
281: {
282: Util::deleteCookieHeader($this->header, $name, $value);
283: }
284:
285: /**
286: * Redirect
287: *
288: * This method prepares this response to return an HTTP Redirect response
289: * to the HTTP client.
290: *
291: * @param string $url The redirect destination
292: * @param int $status The redirect HTTP status code
293: */
294: public function redirect ($url, $status = 302)
295: {
296: $this->status = $status;
297: $this['Location'] = $url;
298: }
299:
300: /**
301: * Helpers: Empty?
302: * @return bool
303: */
304: public function isEmpty()
305: {
306: return in_array($this->status, array(201, 204, 304));
307: }
308:
309: /**
310: * Helpers: Informational?
311: * @return bool
312: */
313: public function isInformational()
314: {
315: return $this->status >= 100 && $this->status < 200;
316: }
317:
318: /**
319: * Helpers: OK?
320: * @return bool
321: */
322: public function isOk()
323: {
324: return $this->status === 200;
325: }
326:
327: /**
328: * Helpers: Successful?
329: * @return bool
330: */
331: public function isSuccessful()
332: {
333: return $this->status >= 200 && $this->status < 300;
334: }
335:
336: /**
337: * Helpers: Redirect?
338: * @return bool
339: */
340: public function isRedirect()
341: {
342: return in_array($this->status, array(301, 302, 303, 307));
343: }
344:
345: /**
346: * Helpers: Redirection?
347: * @return bool
348: */
349: public function isRedirection()
350: {
351: return $this->status >= 300 && $this->status < 400;
352: }
353:
354: /**
355: * Helpers: Forbidden?
356: * @return bool
357: */
358: public function isForbidden()
359: {
360: return $this->status === 403;
361: }
362:
363: /**
364: * Helpers: Not Found?
365: * @return bool
366: */
367: public function isNotFound()
368: {
369: return $this->status === 404;
370: }
371:
372: /**
373: * Helpers: Client error?
374: * @return bool
375: */
376: public function isClientError()
377: {
378: return $this->status >= 400 && $this->status < 500;
379: }
380:
381: /**
382: * Helpers: Server Error?
383: * @return bool
384: */
385: public function isServerError()
386: {
387: return $this->status >= 500 && $this->status < 600;
388: }
389:
390: /**
391: * Array Access: Offset Exists
392: */
393: public function offsetExists( $offset )
394: {
395: return isset($this->header[$offset]);
396: }
397:
398: /**
399: * Array Access: Offset Get
400: */
401: public function offsetGet( $offset )
402: {
403: if (isset($this->header[$offset])) {
404: return $this->header[$offset];
405: } else {
406: return null;
407: }
408: }
409:
410: /**
411: * Array Access: Offset Set
412: */
413: public function offsetSet($offset, $value)
414: {
415: $this->header[$offset] = $value;
416: }
417:
418: /**
419: * Array Access: Offset Unset
420: */
421: public function offsetUnset($offset)
422: {
423: unset($this->header[$offset]);
424: }
425:
426: /**
427: * Countable: Count
428: */
429: public function count()
430: {
431: return count($this->header);
432: }
433:
434: /**
435: * Get Iterator
436: *
437: * This returns the contained `\Slim\Http\Headers` instance which
438: * is itself iterable.
439: *
440: * @return \Slim\Http\Headers
441: */
442: public function getIterator()
443: {
444: return $this->header;
445: }
446:
447: /**
448: * Get message for HTTP status code
449: * @return string|null
450: */
451: public static function getMessageForCode($status)
452: {
453: if (isset(self::$messages[$status])) {
454: return self::$messages[$status];
455: } else {
456: return null;
457: }
458: }
459: }
460: