PageRenderTime 60ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/Slim/Http/Response.php

http://github.com/codeguy/Slim
PHP | 480 lines | 212 code | 41 blank | 227 comment | 20 complexity | da31b50d9e6bc2d59621e45d1bef29b5 MD5 | raw file
  1. <?php
  2. /**
  3. * Slim Framework (https://slimframework.com)
  4. *
  5. * @link https://github.com/slimphp/Slim
  6. * @copyright Copyright (c) 2011-2017 Josh Lockhart
  7. * @license https://github.com/slimphp/Slim/blob/3.x/LICENSE.md (MIT License)
  8. */
  9. namespace Slim\Http;
  10. use InvalidArgumentException;
  11. use Psr\Http\Message\ResponseInterface;
  12. use Psr\Http\Message\StreamInterface;
  13. use Psr\Http\Message\UriInterface;
  14. use Slim\Interfaces\Http\HeadersInterface;
  15. /**
  16. * Response
  17. *
  18. * This class represents an HTTP response. It manages
  19. * the response status, headers, and body
  20. * according to the PSR-7 standard.
  21. *
  22. * @link https://github.com/php-fig/http-message/blob/master/src/MessageInterface.php
  23. * @link https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php
  24. */
  25. class Response extends Message implements ResponseInterface
  26. {
  27. /**
  28. * Status code
  29. *
  30. * @var int
  31. */
  32. protected $status = 200;
  33. /**
  34. * Reason phrase
  35. *
  36. * @var string
  37. */
  38. protected $reasonPhrase = '';
  39. /**
  40. * Status codes and reason phrases
  41. *
  42. * @var array
  43. */
  44. protected static $messages = [
  45. //Informational 1xx
  46. 100 => 'Continue',
  47. 101 => 'Switching Protocols',
  48. 102 => 'Processing',
  49. //Successful 2xx
  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. 207 => 'Multi-Status',
  58. 208 => 'Already Reported',
  59. 226 => 'IM Used',
  60. //Redirection 3xx
  61. 300 => 'Multiple Choices',
  62. 301 => 'Moved Permanently',
  63. 302 => 'Found',
  64. 303 => 'See Other',
  65. 304 => 'Not Modified',
  66. 305 => 'Use Proxy',
  67. 306 => '(Unused)',
  68. 307 => 'Temporary Redirect',
  69. 308 => 'Permanent Redirect',
  70. //Client Error 4xx
  71. 400 => 'Bad Request',
  72. 401 => 'Unauthorized',
  73. 402 => 'Payment Required',
  74. 403 => 'Forbidden',
  75. 404 => 'Not Found',
  76. 405 => 'Method Not Allowed',
  77. 406 => 'Not Acceptable',
  78. 407 => 'Proxy Authentication Required',
  79. 408 => 'Request Timeout',
  80. 409 => 'Conflict',
  81. 410 => 'Gone',
  82. 411 => 'Length Required',
  83. 412 => 'Precondition Failed',
  84. 413 => 'Request Entity Too Large',
  85. 414 => 'Request-URI Too Long',
  86. 415 => 'Unsupported Media Type',
  87. 416 => 'Requested Range Not Satisfiable',
  88. 417 => 'Expectation Failed',
  89. 418 => 'I\'m a teapot',
  90. 421 => 'Misdirected Request',
  91. 422 => 'Unprocessable Entity',
  92. 423 => 'Locked',
  93. 424 => 'Failed Dependency',
  94. 426 => 'Upgrade Required',
  95. 428 => 'Precondition Required',
  96. 429 => 'Too Many Requests',
  97. 431 => 'Request Header Fields Too Large',
  98. 444 => 'Connection Closed Without Response',
  99. 451 => 'Unavailable For Legal Reasons',
  100. 499 => 'Client Closed Request',
  101. //Server Error 5xx
  102. 500 => 'Internal Server Error',
  103. 501 => 'Not Implemented',
  104. 502 => 'Bad Gateway',
  105. 503 => 'Service Unavailable',
  106. 504 => 'Gateway Timeout',
  107. 505 => 'HTTP Version Not Supported',
  108. 506 => 'Variant Also Negotiates',
  109. 507 => 'Insufficient Storage',
  110. 508 => 'Loop Detected',
  111. 510 => 'Not Extended',
  112. 511 => 'Network Authentication Required',
  113. 599 => 'Network Connect Timeout Error',
  114. ];
  115. /**
  116. * EOL characters used for HTTP response.
  117. *
  118. * @var string
  119. */
  120. const EOL = "\r\n";
  121. /**
  122. * Create new HTTP response.
  123. *
  124. * @param int $status The response status code.
  125. * @param HeadersInterface|null $headers The response headers.
  126. * @param StreamInterface|null $body The response body.
  127. */
  128. public function __construct($status = 200, HeadersInterface $headers = null, StreamInterface $body = null)
  129. {
  130. $this->status = $this->filterStatus($status);
  131. $this->headers = $headers ? $headers : new Headers();
  132. $this->body = $body ? $body : new Body(fopen('php://temp', 'r+'));
  133. }
  134. /**
  135. * This method is applied to the cloned object
  136. * after PHP performs an initial shallow-copy. This
  137. * method completes a deep-copy by creating new objects
  138. * for the cloned object's internal reference pointers.
  139. */
  140. public function __clone()
  141. {
  142. $this->headers = clone $this->headers;
  143. }
  144. /*******************************************************************************
  145. * Status
  146. ******************************************************************************/
  147. /**
  148. * Gets the response status code.
  149. *
  150. * The status code is a 3-digit integer result code of the server's attempt
  151. * to understand and satisfy the request.
  152. *
  153. * @return int Status code.
  154. */
  155. public function getStatusCode()
  156. {
  157. return $this->status;
  158. }
  159. /**
  160. * Return an instance with the specified status code and, optionally, reason phrase.
  161. *
  162. * If no reason phrase is specified, implementations MAY choose to default
  163. * to the RFC 7231 or IANA recommended reason phrase for the response's
  164. * status code.
  165. *
  166. * This method MUST be implemented in such a way as to retain the
  167. * immutability of the message, and MUST return an instance that has the
  168. * updated status and reason phrase.
  169. *
  170. * @link http://tools.ietf.org/html/rfc7231#section-6
  171. * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  172. * @param int $code The 3-digit integer result code to set.
  173. * @param string $reasonPhrase The reason phrase to use with the
  174. * provided status code; if none is provided, implementations MAY
  175. * use the defaults as suggested in the HTTP specification.
  176. * @return static
  177. * @throws \InvalidArgumentException For invalid status code arguments.
  178. */
  179. public function withStatus($code, $reasonPhrase = '')
  180. {
  181. $code = $this->filterStatus($code);
  182. if (!is_string($reasonPhrase) && !method_exists($reasonPhrase, '__toString')) {
  183. throw new InvalidArgumentException('ReasonPhrase must be a string');
  184. }
  185. $clone = clone $this;
  186. $clone->status = $code;
  187. if ($reasonPhrase === '' && isset(static::$messages[$code])) {
  188. $reasonPhrase = static::$messages[$code];
  189. }
  190. if ($reasonPhrase === '') {
  191. throw new InvalidArgumentException('ReasonPhrase must be supplied for this code');
  192. }
  193. $clone->reasonPhrase = $reasonPhrase;
  194. return $clone;
  195. }
  196. /**
  197. * Filter HTTP status code.
  198. *
  199. * @param int $status HTTP status code.
  200. * @return int
  201. * @throws \InvalidArgumentException If an invalid HTTP status code is provided.
  202. */
  203. protected function filterStatus($status)
  204. {
  205. if (!is_integer($status) || $status<100 || $status>599) {
  206. throw new InvalidArgumentException('Invalid HTTP status code');
  207. }
  208. return $status;
  209. }
  210. /**
  211. * Gets the response reason phrase associated with the status code.
  212. *
  213. * Because a reason phrase is not a required element in a response
  214. * status line, the reason phrase value MAY be null. Implementations MAY
  215. * choose to return the default RFC 7231 recommended reason phrase (or those
  216. * listed in the IANA HTTP Status Code Registry) for the response's
  217. * status code.
  218. *
  219. * @link http://tools.ietf.org/html/rfc7231#section-6
  220. * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  221. * @return string Reason phrase; must return an empty string if none present.
  222. */
  223. public function getReasonPhrase()
  224. {
  225. if ($this->reasonPhrase) {
  226. return $this->reasonPhrase;
  227. }
  228. if (isset(static::$messages[$this->status])) {
  229. return static::$messages[$this->status];
  230. }
  231. return '';
  232. }
  233. /*******************************************************************************
  234. * Body
  235. ******************************************************************************/
  236. /**
  237. * Write data to the response body.
  238. *
  239. * Note: This method is not part of the PSR-7 standard.
  240. *
  241. * Proxies to the underlying stream and writes the provided data to it.
  242. *
  243. * @param string $data
  244. * @return $this
  245. */
  246. public function write($data)
  247. {
  248. $this->getBody()->write($data);
  249. return $this;
  250. }
  251. /*******************************************************************************
  252. * Response Helpers
  253. ******************************************************************************/
  254. /**
  255. * Redirect.
  256. *
  257. * Note: This method is not part of the PSR-7 standard.
  258. *
  259. * This method prepares the response object to return an HTTP Redirect
  260. * response to the client.
  261. *
  262. * @param string|UriInterface $url The redirect destination.
  263. * @param int|null $status The redirect HTTP status code.
  264. * @return static
  265. */
  266. public function withRedirect($url, $status = null)
  267. {
  268. $responseWithRedirect = $this->withHeader('Location', (string)$url);
  269. if (is_null($status) && $this->getStatusCode() === 200) {
  270. $status = 302;
  271. }
  272. if (!is_null($status)) {
  273. return $responseWithRedirect->withStatus($status);
  274. }
  275. return $responseWithRedirect;
  276. }
  277. /**
  278. * Json.
  279. *
  280. * Note: This method is not part of the PSR-7 standard.
  281. *
  282. * This method prepares the response object to return an HTTP Json
  283. * response to the client.
  284. *
  285. * @param mixed $data The data
  286. * @param int $status The HTTP status code.
  287. * @param int $encodingOptions Json encoding options
  288. * @throws \RuntimeException
  289. * @return static
  290. */
  291. public function withJson($data, $status = null, $encodingOptions = 0)
  292. {
  293. $response = $this->withBody(new Body(fopen('php://temp', 'r+')));
  294. $response->body->write($json = json_encode($data, $encodingOptions));
  295. // Ensure that the json encoding passed successfully
  296. if ($json === false) {
  297. throw new \RuntimeException(json_last_error_msg(), json_last_error());
  298. }
  299. $responseWithJson = $response->withHeader('Content-Type', 'application/json;charset=utf-8');
  300. if (isset($status)) {
  301. return $responseWithJson->withStatus($status);
  302. }
  303. return $responseWithJson;
  304. }
  305. /**
  306. * Is this response empty?
  307. *
  308. * Note: This method is not part of the PSR-7 standard.
  309. *
  310. * @return bool
  311. */
  312. public function isEmpty()
  313. {
  314. return in_array($this->getStatusCode(), [204, 205, 304]);
  315. }
  316. /**
  317. * Is this response informational?
  318. *
  319. * Note: This method is not part of the PSR-7 standard.
  320. *
  321. * @return bool
  322. */
  323. public function isInformational()
  324. {
  325. return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
  326. }
  327. /**
  328. * Is this response OK?
  329. *
  330. * Note: This method is not part of the PSR-7 standard.
  331. *
  332. * @return bool
  333. */
  334. public function isOk()
  335. {
  336. return $this->getStatusCode() === 200;
  337. }
  338. /**
  339. * Is this response successful?
  340. *
  341. * Note: This method is not part of the PSR-7 standard.
  342. *
  343. * @return bool
  344. */
  345. public function isSuccessful()
  346. {
  347. return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
  348. }
  349. /**
  350. * Is this response a redirect?
  351. *
  352. * Note: This method is not part of the PSR-7 standard.
  353. *
  354. * @return bool
  355. */
  356. public function isRedirect()
  357. {
  358. return in_array($this->getStatusCode(), [301, 302, 303, 307]);
  359. }
  360. /**
  361. * Is this response a redirection?
  362. *
  363. * Note: This method is not part of the PSR-7 standard.
  364. *
  365. * @return bool
  366. */
  367. public function isRedirection()
  368. {
  369. return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
  370. }
  371. /**
  372. * Is this response forbidden?
  373. *
  374. * Note: This method is not part of the PSR-7 standard.
  375. *
  376. * @return bool
  377. * @api
  378. */
  379. public function isForbidden()
  380. {
  381. return $this->getStatusCode() === 403;
  382. }
  383. /**
  384. * Is this response not Found?
  385. *
  386. * Note: This method is not part of the PSR-7 standard.
  387. *
  388. * @return bool
  389. */
  390. public function isNotFound()
  391. {
  392. return $this->getStatusCode() === 404;
  393. }
  394. /**
  395. * Is this response a client error?
  396. *
  397. * Note: This method is not part of the PSR-7 standard.
  398. *
  399. * @return bool
  400. */
  401. public function isClientError()
  402. {
  403. return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
  404. }
  405. /**
  406. * Is this response a server error?
  407. *
  408. * Note: This method is not part of the PSR-7 standard.
  409. *
  410. * @return bool
  411. */
  412. public function isServerError()
  413. {
  414. return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
  415. }
  416. /**
  417. * Convert response to string.
  418. *
  419. * Note: This method is not part of the PSR-7 standard.
  420. *
  421. * @return string
  422. */
  423. public function __toString()
  424. {
  425. $output = sprintf(
  426. 'HTTP/%s %s %s',
  427. $this->getProtocolVersion(),
  428. $this->getStatusCode(),
  429. $this->getReasonPhrase()
  430. );
  431. $output .= Response::EOL;
  432. foreach ($this->getHeaders() as $name => $values) {
  433. $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)) . Response::EOL;
  434. }
  435. $output .= Response::EOL;
  436. $output .= (string)$this->getBody();
  437. return $output;
  438. }
  439. }