From 513db2def128737a56d0bd17c7aa0c6460ef404b Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Sun, 22 Mar 2015 20:42:09 -0400 Subject: [PATCH] Add Message\ServerRequest --- src/Message/ServerRequest.php | 324 +++++++++++++++++ test/tests/unit/Message/ServerRequestTest.php | 340 ++++++++++++++++++ 2 files changed, 664 insertions(+) create mode 100644 src/Message/ServerRequest.php create mode 100644 test/tests/unit/Message/ServerRequestTest.php diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php new file mode 100644 index 0000000..f9ea6e4 --- /dev/null +++ b/src/Message/ServerRequest.php @@ -0,0 +1,324 @@ +attributes = []; + } + + // Psr\Http\Message\ServerRequestInterface ------------------------------------------------------------------------- + + /** + * Retrieve server parameters. + * + * Retrieves data related to the incoming request environment, + * typically derived from PHP's $_SERVER superglobal. The data IS NOT + * REQUIRED to originate from $_SERVER. + * + * @return array + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * Retrieve cookies. + * + * Retrieves cookies sent by the client to the server. + * + * The data MUST be compatible with the structure of the $_COOKIE + * superglobal. + * + * @return array + */ + public function getCookieParams() + { + return $this->cookieParams; + } + + /** + * Create a new instance with the specified cookies. + * + * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST + * be compatible with the structure of $_COOKIE. Typically, this data will + * be injected at instantiation. + * + * 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 cookie values. + * + * @param array $cookies Array of key/value pairs representing cookies. + * @return self + */ + public function withCookieParams(array $cookies) + { + $request = clone $this; + $request->cookieParams = $cookies; + return $request; + } + + /** + * Retrieve query string arguments. + * + * Retrieves the deserialized query string arguments, if any. + * + * Note: the query params might not be in sync with the URL or server + * params. If you need to ensure you are only getting the original + * values, you may need to parse the composed URL or the `QUERY_STRING` + * composed in the server params. + * + * @return array + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * Create a new instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URL stored by the + * request, nor the values in the server params. + * + * 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 query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return self + */ + public function withQueryParams(array $query) + { + $request = clone $this; + $request->queryParams = $query; + return $request; + } + + /** + * Retrieve the upload file metadata. + * + * This method MUST return file upload metadata in the same structure + * as PHP's $_FILES superglobal. + * + * These values MUST remain immutable over the course of the incoming + * request. They SHOULD be injected during instantiation, such as from PHP's + * $_FILES superglobal, but MAY be derived from other sources. + * + * @return array Upload file(s) metadata, if any. + */ + public function getFileParams() + { + return $this->fileParams; + } + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody() + { + if (!isset($this->parsedBody)) { + $contentType = $this->getHeader("Content-type"); + if ($contentType === "application/x-www-form-urlencoded" || $contentType === "multipart/form-data") { + $this->parsedBody = $_POST; + } + } + return $this->parsedBody; + } + + /** + * Create a new instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * 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 body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return self + */ + public function withParsedBody($data) + { + $request = clone $this; + $request->parsedBody = $data; + return $request; + } + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null) + { + if (isset($this->attributes[$name])) { + return $this->attributes[$name]; + } + return $default; + } + + /** + * Create a new instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * 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 attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return self + */ + public function withAttribute($name, $value) + { + $request = clone $this; + $request->attributes[$name] = $value; + return $request; + } + + /** + * Create a new instance that removes the specified derived request + * attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return self + */ + public function withoutAttribute($name) + { + $request = clone $this; + unset($request->attributes[$name]); + return $request; + } + + // ------------------------------------------------------------------------- + + /** + * Return a reference to the singleton instance of the Request derived + * from the server's information about the request sent to the server. + * + * @return self + * @static + */ + public static function getServerRequest() + { + if (!isset(self::$serverRequest)) { + $request = new self(); + $request->serverParams = $_SERVER; + $request->cookieParams = $_COOKIE; + $request->fileParams = $_FILES; + $request->queryParams = []; + if (isset($_SERVER["QUERY_STRING"])) { + parse_str($_SERVER["QUERY_STRING"], $request->queryParams); + } + self::$serverRequest = $request; + } + return self::$serverRequest; + } + + public function __clone() + { + if (is_object($this->parsedBody)) { + $this->parsedBody = clone $this->parsedBody; + } + parent::__clone(); + } +} diff --git a/test/tests/unit/Message/ServerRequestTest.php b/test/tests/unit/Message/ServerRequestTest.php new file mode 100644 index 0000000..61c1bb6 --- /dev/null +++ b/test/tests/unit/Message/ServerRequestTest.php @@ -0,0 +1,340 @@ +assertNotNull($request); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getServerRequest + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + * @preserveGlobalState disabled + */ + public function testServerRequestProvidesRequest() + { + $_SERVER = [ + "HTTP_HOST" => "localhost", + "QUERY_STRING" => "guinea_pig=Claude&hamster=Fizzgig" + ]; + $_COOKIE = [ + "cat" => "Molly" + ]; + $_FILES = [ + "file" => [ + "name" => "MyFile.jpg", + "type" => "image/jpeg", + "tmp_name" => "/tmp/php/php6hst32", + "error" => "UPLOAD_ERR_OK", + "size" => 98174 + ] + ]; + $_POST = [ + "dog" => "Bear" + ]; + $request = ServerRequest::getServerRequest(); + $this->assertNotNull($request); + return $request; + } + + /** + * @covers WellRESTed\Message\ServerRequest::getServerParams + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testServerRequestProvidesServerParams($request) + { + /** @var ServerRequest $request */ + $this->assertEquals("localhost", $request->getServerParams()["HTTP_HOST"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getCookieParams + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testServerRequestProvidesCookieParams($request) + { + /** @var ServerRequest $request */ + $this->assertEquals("Molly", $request->getCookieParams()["cat"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getQueryParams + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testServerRequestProvidesQueryParams($request) + { + /** @var ServerRequest $request */ + $this->assertEquals("Claude", $request->getQueryParams()["guinea_pig"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getFileParams + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testServerRequestProvidesFilesParams($request) + { + /** @var ServerRequest $request */ + $this->assertEquals("MyFile.jpg", $request->getFileParams()["file"]["name"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withCookieParams + * @uses WellRESTed\Message\ServerRequest::getCookieParams + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testWithCookieParamsCreatesNewInstance($request1) + { + /** @var ServerRequest $request1 */ + $request2 = $request1->withCookieParams([ + "cat" => "Oscar" + ]); + $this->assertEquals("Molly", $request1->getCookieParams()["cat"]); + $this->assertEquals("Oscar", $request2->getCookieParams()["cat"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withQueryParams + * @uses WellRESTed\Message\ServerRequest::getQueryParams + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testWithQueryParamsCreatesNewInstance($request1) + { + /** @var ServerRequest $request1 */ + $request2 = $request1->withQueryParams([ + "guinea_pig" => "Clyde" + ]); + $this->assertEquals("Claude", $request1->getQueryParams()["guinea_pig"]); + $this->assertEquals("Clyde", $request2->getQueryParams()["guinea_pig"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testGetParsedBodyReturnsFormFieldsForUrlencodedForm($request) + { + $_POST = [ + "dog" => "Bear" + ]; + + /** @var ServerRequest $request */ + $request = $request->withHeader("Content-type", "application/x-www-form-urlencoded"); + $this->assertEquals("Bear", $request->getParsedBody()["dog"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testGetParsedBodyReturnsFormFieldsForMultipartForm($request) + { + $_POST = [ + "dog" => "Bear" + ]; + + /** @var ServerRequest $request */ + $request = $request->withHeader("Content-type", "multipart/form-data"); + $this->assertEquals("Bear", $request->getParsedBody()["dog"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + * @preserveGlobalState disabled + * @depends testServerRequestProvidesRequest + */ + public function testWithParsedBodyCreatesNewInstance($request1) + { + $_POST = [ + "dog" => "Bear" + ]; + + /** @var ServerRequest $request1 */ + $request1 = $request1->withHeader("Content-type", "application/x-www-form-urlencoded"); + $body1 = $request1->getParsedBody(); + $request2 = $request1->withParsedBody([ + "guinea_pig" => "Clyde" + ]); + $body2 = $request2->getParsedBody(); + + $this->assertEquals("Bear", $body1["dog"]); + $this->assertEquals("Clyde", $body2["guinea_pig"]); + } + + /** + * @covers WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testCloneMakesDeepCopiesOfParsedBody() + { + $body = (object) [ + "cat" => "Dog" + ]; + + $request1 = new ServerRequest(); + $request1 = $request1->withParsedBody($body); + $request2 = $request1->withHeader("X-extra", "hello world"); + $this->assertEquals($request1->getParsedBody(), $request2->getParsedBody()); + $this->assertNotSame($request1->getParsedBody(), $request2->getParsedBody()); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withAttribute + * @covers WellRESTed\Message\ServerRequest::getAttribute + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testWithAttributeCreatesNewInstance() + { + $request = new ServerRequest(); + $request = $request->withAttribute("cat", "Molly"); + $this->assertEquals("Molly", $request->getAttribute("cat")); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withAttribute + * @uses WellRESTed\Message\ServerRequest::getAttribute + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testWithAttributePreserversOtherAttributes() + { + $request = new ServerRequest(); + $request = $request->withAttribute("cat", "Molly"); + $request = $request->withAttribute("dog", "Bear"); + $this->assertEquals("Molly", $request->getAttribute("cat")); + $this->assertEquals("Bear", $request->getAttribute("dog")); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getAttribute + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testGetAttributeReturnsDefaultIfNotSet() + { + $request = new ServerRequest(); + $this->assertEquals("Oscar", $request->getAttribute("cat", "Oscar")); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withoutAttribute + * @uses WellRESTed\Message\ServerRequest::withAttribute + * @uses WellRESTed\Message\ServerRequest::getAttribute + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testWithoutAttributeCreatesNewInstance() + { + $request = new ServerRequest(); + $request = $request->withAttribute("cat", "Molly"); + $request = $request->withoutAttribute("cat"); + $this->assertEquals("Oscar", $request->getAttribute("cat", "Oscar")); + } + + /** + * @covers WellRESTed\Message\ServerRequest::withoutAttribute + * @uses WellRESTed\Message\ServerRequest::withAttribute + * @uses WellRESTed\Message\ServerRequest::getAttribute + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testWithoutAttributePreservesOtherAttributes() + { + $request = new ServerRequest(); + $request = $request->withAttribute("cat", "Molly"); + $request = $request->withAttribute("dog", "Bear"); + $request = $request->withoutAttribute("cat"); + $this->assertEquals("Bear", $request->getAttribute("dog")); + $this->assertEquals("Oscar", $request->getAttribute("cat", "Oscar")); + } + + /** + * @covers WellRESTed\Message\ServerRequest::getAttributes + * @uses WellRESTed\Message\ServerRequest::withAttribute + * @uses WellRESTed\Message\ServerRequest::__construct + * @uses WellRESTed\Message\ServerRequest::__clone + * @uses WellRESTed\Message\ServerRequest::withParsedBody + * @uses WellRESTed\Message\ServerRequest::getParsedBody + * @uses WellRESTed\Message\Request + * @uses WellRESTed\Message\Message + * @uses WellRESTed\Message\HeaderCollection + */ + public function testGetAttributesReturnsAllAttributes() + { + $request = new ServerRequest(); + $request = $request->withAttribute("cat", "Molly"); + $request = $request->withAttribute("dog", "Bear"); + $attributes = $request->getAttributes(); + $this->assertEquals("Molly", $attributes["cat"]); + $this->assertEquals("Bear", $attributes["dog"]); + } + + +}