Response.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\action;
  9. /**
  10. * A `Response` object is typically instantiated automatically by the `Controller`. It is assigned
  11. * any headers set in the course of the request, as well as any content rendered by the
  12. * `Controller`. Once completed, the `Controller` returns the `Response` object to the `Dispatcher`.
  13. *
  14. * The `Response` object is responsible for writing its body content to output, and writing any
  15. * headers to the browser.
  16. *
  17. * @see lithium\action\Dispatcher
  18. * @see lithium\action\Controller
  19. */
  20. class Response extends \lithium\net\http\Response {
  21. /**
  22. * Classes used by Response.
  23. *
  24. * @var array
  25. */
  26. protected $_classes = array(
  27. 'router' => 'lithium\net\http\Router',
  28. 'media' => 'lithium\net\http\Media'
  29. );
  30. protected $_autoConfig = array('classes' => 'merge');
  31. public function __construct(array $config = array()) {
  32. $defaults = array(
  33. 'buffer' => 8192,
  34. 'location' => null,
  35. 'status' => 0,
  36. 'request' => null,
  37. 'decode' => false
  38. );
  39. parent::__construct($config + $defaults);
  40. }
  41. protected function _init() {
  42. parent::_init();
  43. $config = $this->_config;
  44. $this->status($config['status']);
  45. unset($this->_config['status']);
  46. if ($config['location']) {
  47. $classes = $this->_classes;
  48. $location = $classes['router']::match($config['location'], $config['request']);
  49. $this->headers('Location', $location);
  50. }
  51. }
  52. /**
  53. * Controls how or whether the client browser and web proxies should cache this response.
  54. *
  55. * @param mixed $expires This can be a Unix timestamp indicating when the page expires, or a
  56. * string indicating the relative time offset that a page should expire, i.e.
  57. * `"+5 hours". Finally, `$expires` can be set to `false` to completely disable
  58. * browser or proxy caching.
  59. * @return void
  60. */
  61. public function cache($expires) {
  62. if ($expires === false) {
  63. return $this->headers(array(
  64. 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
  65. 'Cache-Control' => array(
  66. 'no-store, no-cache, must-revalidate',
  67. 'post-check=0, pre-check=0',
  68. 'max-age=0'
  69. ),
  70. 'Pragma' => 'no-cache'
  71. ));
  72. }
  73. $expires = is_int($expires) ? $expires : strtotime($expires);
  74. return $this->headers(array(
  75. 'Expires' => gmdate('D, d M Y H:i:s', $expires) . ' GMT',
  76. 'Cache-Control' => 'max-age=' . ($expires - time()),
  77. 'Pragma' => 'cache'
  78. ));
  79. }
  80. /**
  81. * Sets/Gets the content type. If `'type'` is null, the method will attempt to determine the
  82. * type from the params, then from the environment setting
  83. *
  84. * @param string $type a full content type i.e. `'application/json'` or simple name `'json'`
  85. * @return string A simple content type name, i.e. `'html'`, `'xml'`, `'json'`, etc., depending
  86. * on the content type of the request.
  87. */
  88. public function type($type = null) {
  89. if ($type === null && $this->_type === null) {
  90. $type = 'html';
  91. }
  92. return parent::type($type);
  93. }
  94. /**
  95. * Render a response by writing headers and output. Output is echoed in chunks because of an
  96. * issue where `echo` time increases exponentially on long message bodies.
  97. *
  98. * @return void
  99. */
  100. public function render() {
  101. $code = null;
  102. $hasLocation = (isset($this->headers['location']) || isset($this->headers['Location']));
  103. if ($hasLocation && $this->status['code'] === 200) {
  104. $code = 302;
  105. }
  106. $this->_writeHeader($this->status($code) ?: $this->status(500));
  107. foreach ($this->headers as $name => $value) {
  108. $key = strtolower($name);
  109. if ($key == 'location') {
  110. $this->_writeHeader("Location: {$value}", $this->status['code']);
  111. } elseif ($key == 'download') {
  112. $this->_writeHeader('Content-Disposition: attachment; filename="' . $value . '"');
  113. } elseif (is_array($value)) {
  114. $this->_writeHeader(
  115. array_map(function($v) use ($name) { return "{$name}: {$v}"; }, $value)
  116. );
  117. } elseif (!is_numeric($name)) {
  118. $this->_writeHeader("{$name}: {$value}");
  119. }
  120. }
  121. if ($code == 302 || $code == 204) {
  122. return;
  123. }
  124. $chunked = $this->body(null, $this->_config);
  125. foreach ($chunked as $chunk) {
  126. echo $chunk;
  127. }
  128. }
  129. /**
  130. * Casts the Response object to a string. This doesn't actually return a string, but does
  131. * a direct render and returns null.
  132. *
  133. * @return string An empty string.
  134. */
  135. public function __toString() {
  136. $this->render();
  137. return '';
  138. }
  139. /**
  140. * Writes raw headers to output.
  141. *
  142. * @param string|array $header Either a raw header string, or an array of header strings. Use
  143. * an array if a single header must be written multiple times with different values.
  144. * Otherwise, additional values for duplicate headers will overwrite previous values.
  145. * @param integer $code Optional. If present, forces a specific HTTP response code. Used
  146. * primarily in conjunction with the 'Location' header.
  147. * @return void
  148. */
  149. protected function _writeHeader($header, $code = null) {
  150. if (is_array($header)) {
  151. array_map(function($h) { header($h, false); }, $header);
  152. return;
  153. }
  154. $code ? header($header, true, $code) : header($header, true);
  155. }
  156. }
  157. ?>