diff --git a/src/Message/Response.php b/src/Message/Response.php new file mode 100644 index 0000000..2029698 --- /dev/null +++ b/src/Message/Response.php @@ -0,0 +1,153 @@ +statusCode; + } + + /** + * Create a new instance with the specified status code, and optionally + * reason phrase, for the response. + * + * If no Reason-Phrase is specified, implementations MAY choose to default + * to the RFC 7231 or IANA recommended reason phrase for the response's + * Status-Code. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * updated status and reason phrase. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @param integer $code The 3-digit integer result code to set. + * @param null|string $reasonPhrase The reason phrase to use with the + * provided status code; if none is provided, implementations MAY + * use the defaults as suggested in the HTTP specification. + * @return self + * @throws \InvalidArgumentException For invalid status code arguments. + */ + public function withStatus($code, $reasonPhrase = null) + { + $response = clone $this; + $response->statusCode = $code; + if ($reasonPhrase === null) { + static $reasonPhraseLookup = null; + if ($reasonPhraseLookup === null) { + $reasonPhraseLookup = [ + 100 => "Continue", + 101 => "Switching Protocols", + 102 => "Processing", + 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", + 300 => "Multiple Choices", + 301 => "Moved Permanently", + 302 => "Found", + 303 => "See Other", + 304 => "Not Modified", + 305 => "Use Proxy", + 307 => "Temporary Redirect", + 308 => "Permanent Redirect", + 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", + 421 => "Misdirected Request", + 422 => "Unprocessable Entity", + 423 => "Locked", + 424 => "Failed Dependency", + 426 => "Upgrade Required", + 428 => "Precondition Required", + 429 => "Too Many Requests", + 431 => "Request Header Fields Too Large", + 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" + ]; + } + if (isset($reasonPhraseLookup[$code])) { + $reasonPhrase = $reasonPhraseLookup[$code]; + } else { + $reasonPhrase = "Unknown"; + } + } + $response->reasonPhrase = $reasonPhrase; + return $response; + } + + /** + * Gets the response Reason-Phrase, a short textual description of the Status-Code. + * + * Because a Reason-Phrase is not a required element in a response + * Status-Line, the Reason-Phrase value MAY be null. Implementations MAY + * choose to return the default RFC 7231 recommended reason phrase (or those + * listed in the IANA HTTP Status Code Registry) for the response's + * Status-Code. + * + * @link http://tools.ietf.org/html/rfc7231#section-6 + * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * @return string|null Reason phrase, or null if unknown. + */ + public function getReasonPhrase() + { + return $this->reasonPhrase; + } +} diff --git a/test/tests/unit/Message/ResponseTest.php b/test/tests/unit/Message/ResponseTest.php new file mode 100644 index 0000000..923807e --- /dev/null +++ b/test/tests/unit/Message/ResponseTest.php @@ -0,0 +1,106 @@ +withStatus(200); + $this->assertEquals(200, $copy->getStatusCode()); + } + + /** + * @covers WellRESTed\Message\Response::withStatus + * @covers WellRESTed\Message\Response::getReasonPhrase + * @uses WellRESTed\Message\Response::__clone + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + * @dataProvider statusProvider + */ + public function testCreatesNewInstanceWithReasonPhrase($code, $reasonPhrase, $expected) + { + $response = new Response(); + $copy = $response->withStatus($code, $reasonPhrase); + $this->assertEquals($expected, $copy->getReasonPhrase()); + } + + public function statusProvider() + { + return [ + [100, null, "Continue"], + [101, null, "Switching Protocols"], + [200, null, "OK"], + [201, null, "Created"], + [202, null, "Accepted"], + [203, null, "Non-Authoritative Information"], + [204, null, "No Content"], + [205, null, "Reset Content"], + [206, null, "Partial Content"], + [300, null, "Multiple Choices"], + [301, null, "Moved Permanently"], + [302, null, "Found"], + [303, null, "See Other"], + [304, null, "Not Modified"], + [305, null, "Use Proxy"], + [400, null, "Bad Request"], + [401, null, "Unauthorized"], + [402, null, "Payment Required"], + [403, null, "Forbidden"], + [404, null, "Not Found"], + [405, null, "Method Not Allowed"], + [406, null, "Not Acceptable"], + [407, null, "Proxy Authentication Required"], + [408, null, "Request Timeout"], + [409, null, "Conflict"], + [410, null, "Gone"], + [411, null, "Length Required"], + [412, null, "Precondition Failed"], + [413, null, "Payload Too Large"], + [414, null, "URI Too Long"], + [415, null, "Unsupported Media Type"], + [500, null, "Internal Server Error"], + [501, null, "Not Implemented"], + [502, null, "Bad Gateway"], + [503, null, "Service Unavailable"], + [504, null, "Gateway Timeout"], + [505, null, "HTTP Version Not Supported"], + [598, null, "Unknown"], + [599, "Nonstandard", "Nonstandard"] + ]; + } + + /** + * @covers WellRESTed\Message\Response::withStatus + * @covers WellRESTed\Message\Response::getStatusCode + * @uses WellRESTed\Message\Response::__clone + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testWithStatusCodePreservesOriginalResponse() + { + $response1 = new Response(); + $response1 = $response1->withStatus(200); + $response1 = $response1->withHeader("Content-type", "application/json"); + + $response2 = $response1->withStatus(404); + $response2 = $response2->withHeader("Content-type", "text/plain"); + + + $this->assertEquals(200, $response1->getStatusCode()); + $this->assertEquals("application/json", $response1->getHeader("Content-type")); + + $this->assertEquals(404, $response2->getStatusCode()); + $this->assertEquals("text/plain", $response2->getHeader("Content-type")); + } +}