--- /dev/null
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
+ * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
+ */
+
+namespace Zend\Diactoros;
+
+use InvalidArgumentException;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * HTTP response encapsulation.
+ *
+ * Responses are considered immutable; all methods that might change state are
+ * implemented such that they retain the internal state of the current
+ * message and return a new instance that contains the changed state.
+ */
+class Response implements ResponseInterface
+{
+ use MessageTrait;
+
+ const MIN_STATUS_CODE_VALUE = 100;
+ const MAX_STATUS_CODE_VALUE = 599;
+
+ /**
+ * Map of standard HTTP status code/reason phrases
+ *
+ * @var array
+ */
+ private $phrases = [
+ // INFORMATIONAL CODES
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ // SUCCESS CODES
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 208 => 'Already Reported',
+ 226 => 'IM Used',
+ // REDIRECTION CODES
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy', // Deprecated to 306 => '(Unused)'
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ // CLIENT ERROR
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Payload Too Large',
+ 414 => 'URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 421 => 'Misdirected Request',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 444 => 'Connection Closed Without Response',
+ 451 => 'Unavailable For Legal Reasons',
+ // SERVER ERROR
+ 499 => 'Client Closed Request',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ 599 => 'Network Connect Timeout Error',
+ ];
+
+ /**
+ * @var string
+ */
+ private $reasonPhrase = '';
+
+ /**
+ * @var int
+ */
+ private $statusCode;
+
+ /**
+ * @param string|resource|StreamInterface $body Stream identifier and/or actual stream resource
+ * @param int $status Status code for the response, if any.
+ * @param array $headers Headers for the response, if any.
+ * @throws InvalidArgumentException on any invalid element.
+ */
+ public function __construct($body = 'php://memory', $status = 200, array $headers = [])
+ {
+ $this->setStatusCode($status);
+ $this->stream = $this->getStream($body, 'wb+');
+ $this->setHeaders($headers);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getReasonPhrase()
+ {
+ if (! $this->reasonPhrase
+ && isset($this->phrases[$this->statusCode])
+ ) {
+ $this->reasonPhrase = $this->phrases[$this->statusCode];
+ }
+
+ return $this->reasonPhrase;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $new = clone $this;
+ $new->setStatusCode($code);
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+
+ /**
+ * Set a valid status code.
+ *
+ * @param int $code
+ * @throws InvalidArgumentException on an invalid status code.
+ */
+ private function setStatusCode($code)
+ {
+ if (! is_numeric($code)
+ || is_float($code)
+ || $code < static::MIN_STATUS_CODE_VALUE
+ || $code > static::MAX_STATUS_CODE_VALUE
+ ) {
+ throw new InvalidArgumentException(sprintf(
+ 'Invalid status code "%s"; must be an integer between %d and %d, inclusive',
+ (is_scalar($code) ? $code : gettype($code)),
+ static::MIN_STATUS_CODE_VALUE,
+ static::MAX_STATUS_CODE_VALUE
+ ));
+ }
+ $this->statusCode = $code;
+ }
+}