From 6b4aff76d05b160b75beb82d5b98bda67a7162a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:07:45 -0600 Subject: [PATCH 01/29] update README repository description and PHP version --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e477aa0..1cf40bf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Slim-Http -Strict PSR-7 implementation used by the Slim Framework, but you may use it -separately with any framework compatible with the PSR-7 standard. +Slim PSR-7 Object Decorators [![Build Status](https://travis-ci.org/slimphp/Slim-Http.svg?branch=master)](https://travis-ci.org/slimphp/Slim-Http) [![Coverage Status](https://coveralls.io/repos/slimphp/Slim-Http/badge.svg?branch=master&service=github)](https://coveralls.io/github/slimphp/Slim-Http?branch=master) From 3072554c4f10dfb813429a44b1151af4a3864599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:08:18 -0600 Subject: [PATCH 02/29] update composer project description and dependencies --- composer.json | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index bd88413..945f279 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "slim/http", "type": "library", - "description": "Slim's PSR-7 implementation", - "keywords": ["psr7","psr-7","http"], + "description": "Slim PSR-7 Object Decorators", + "keywords": ["psr7", "psr-7", "http"], "homepage": "http://slimframework.com", "license": "MIT", "authors": [ @@ -23,20 +23,24 @@ } ], "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" + "ext-json": "^1.6.0", + "ext-libxml": "^7.2", + "ext-SimpleXML": "^7.2", + "php": "^7.1", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "php-http/message-factory": "^1.0" }, "require-dev": { - "squizlabs/php_codesniffer": "^2.5", - "phpunit/phpunit": "^6.0|^7.0", + "http-interop/http-factory-tests": "dev-master", + "phpunit/phpunit": "^7.0", "php-http/psr7-integration-tests": "dev-master", - "phpstan/phpstan": "^0.9" + "nyholm/psr7": "^1.0", + "squizlabs/php_codesniffer": "^2.5", + "zendframework/zend-diactoros": "^2.0" }, "provide": { - "psr/http-message-implementation": "1.0" - }, - "conflict": { - "slim/slim": "^3.0" + "php-http/message-factory": "^1.0" }, "autoload": { "psr-4": { From 54894d05e898a590cb9ca2194ea9775c4fab532a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:09:43 -0600 Subject: [PATCH 03/29] remove all existing Slim-Http src/ and tests/ files --- src/Body.php | 22 - src/Collection.php | 203 ---- src/CollectionInterface.php | 32 - src/Cookies.php | 194 ---- src/CookiesInterface.php | 23 - src/Environment.php | 57 -- src/Headers.php | 240 ----- src/HeadersInterface.php | 22 - src/Message.php | 353 ------- src/Request.php | 1148 ----------------------- src/RequestBody.php | 27 - src/Response.php | 479 ---------- src/Stream.php | 450 --------- src/UploadedFile.php | 333 ------- src/Uri.php | 795 ---------------- tests/BodyTest.php | 416 -------- tests/CollectionTest.php | 156 --- tests/CookiesTest.php | 235 ----- tests/EnvironmentTest.php | 58 -- tests/HeadersTest.php | 251 ----- tests/Integration/BaseTestFactories.php | 51 - tests/Integration/ResponseTest.php | 26 - tests/Integration/ServerRequestTest.php | 27 - tests/Integration/StreamTest.php | 38 - tests/Integration/UploadedFileTest.php | 28 - tests/Integration/UriTest.php | 28 - tests/MessageTest.php | 214 ----- tests/Mocks/MessageStub.php | 38 - tests/RequestBodyTest.php | 334 ------- tests/RequestTest.php | 1094 --------------------- tests/ResponseTest.php | 341 ------- tests/StreamTest.php | 159 ---- tests/UploadedFilesTest.php | 487 ---------- tests/UriTest.php | 612 ------------ tests/getallheaders.php | 7 - 35 files changed, 8978 deletions(-) delete mode 100644 src/Body.php delete mode 100644 src/Collection.php delete mode 100644 src/CollectionInterface.php delete mode 100644 src/Cookies.php delete mode 100644 src/CookiesInterface.php delete mode 100644 src/Environment.php delete mode 100644 src/Headers.php delete mode 100644 src/HeadersInterface.php delete mode 100644 src/Message.php delete mode 100644 src/Request.php delete mode 100644 src/RequestBody.php delete mode 100644 src/Response.php delete mode 100644 src/Stream.php delete mode 100644 src/UploadedFile.php delete mode 100644 src/Uri.php delete mode 100644 tests/BodyTest.php delete mode 100644 tests/CollectionTest.php delete mode 100644 tests/CookiesTest.php delete mode 100644 tests/EnvironmentTest.php delete mode 100644 tests/HeadersTest.php delete mode 100644 tests/Integration/BaseTestFactories.php delete mode 100644 tests/Integration/ResponseTest.php delete mode 100644 tests/Integration/ServerRequestTest.php delete mode 100644 tests/Integration/StreamTest.php delete mode 100644 tests/Integration/UploadedFileTest.php delete mode 100644 tests/Integration/UriTest.php delete mode 100644 tests/MessageTest.php delete mode 100644 tests/Mocks/MessageStub.php delete mode 100644 tests/RequestBodyTest.php delete mode 100644 tests/RequestTest.php delete mode 100755 tests/ResponseTest.php delete mode 100644 tests/StreamTest.php delete mode 100644 tests/UploadedFilesTest.php delete mode 100644 tests/UriTest.php delete mode 100644 tests/getallheaders.php diff --git a/src/Body.php b/src/Body.php deleted file mode 100644 index 93c6068..0000000 --- a/src/Body.php +++ /dev/null @@ -1,22 +0,0 @@ - $value) { - $this->set($key, $value); - } - } - - /******************************************************************************** - * Collection interface - *******************************************************************************/ - - /** - * Set collection item - * - * @param string $key The data key - * @param mixed $value The data value - */ - public function set($key, $value) - { - $this->data[$key] = $value; - } - - /** - * Get collection item for key - * - * @param string $key The data key - * @param mixed $default The default value to return if data key does not exist - * - * @return mixed The key's value, or the default value - */ - public function get($key, $default = null) - { - return $this->has($key) ? $this->data[$key] : $default; - } - - /** - * Add item to collection - * - * @param array $items Key-value array of data to append to this collection - */ - public function replace(array $items) - { - foreach ($items as $key => $value) { - $this->set($key, $value); - } - } - - /** - * Get all items in collection - * - * @return array The collection's source data - */ - public function all() - { - return $this->data; - } - - /** - * Get collection keys - * - * @return array The collection's source data keys - */ - public function keys() - { - return array_keys($this->data); - } - - /** - * Does this collection have a given key? - * - * @param string $key The data key - * - * @return bool - */ - public function has($key) - { - return array_key_exists($key, $this->data); - } - - /** - * Remove item from collection - * - * @param string $key The data key - */ - public function remove($key) - { - unset($this->data[$key]); - } - - /** - * Remove all items from collection - */ - public function clear() - { - $this->data = []; - } - - /******************************************************************************** - * ArrayAccess interface - *******************************************************************************/ - - /** - * Does this collection have a given key? - * - * @param string $key The data key - * - * @return bool - */ - public function offsetExists($key) - { - return $this->has($key); - } - - /** - * Get collection item for key - * - * @param string $key The data key - * - * @return mixed The key's value, or the default value - */ - public function offsetGet($key) - { - return $this->get($key); - } - - /** - * Set collection item - * - * @param string $key The data key - * @param mixed $value The data value - */ - public function offsetSet($key, $value) - { - $this->set($key, $value); - } - - /** - * Remove item from collection - * - * @param string $key The data key - */ - public function offsetUnset($key) - { - $this->remove($key); - } - - /** - * Get number of items in collection - * - * @return int - */ - public function count() - { - return count($this->data); - } - - /******************************************************************************** - * IteratorAggregate interface - *******************************************************************************/ - - /** - * Get collection iterator - * - * @return \ArrayIterator - */ - public function getIterator() - { - return new ArrayIterator($this->data); - } -} diff --git a/src/CollectionInterface.php b/src/CollectionInterface.php deleted file mode 100644 index 3c9c192..0000000 --- a/src/CollectionInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - '', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => false, - 'httponly' => false - ]; - - /** - * Create new cookies helper - * - * @param array $cookies - */ - public function __construct(array $cookies = []) - { - $this->requestCookies = $cookies; - } - - /** - * Set default cookie properties - * - * @param array $settings - */ - public function setDefaults(array $settings) - { - $this->defaults = array_replace($this->defaults, $settings); - } - - /** - * Get request cookie - * - * @param string $name Cookie name - * @param mixed $default Cookie default value - * - * @return mixed Cookie value if present, else default - */ - public function get($name, $default = null) - { - return isset($this->requestCookies[$name]) ? $this->requestCookies[$name] : $default; - } - - /** - * Set response cookie - * - * @param string $name Cookie name - * @param string|array $value Cookie value, or cookie properties - */ - public function set($name, $value) - { - if (!is_array($value)) { - $value = ['value' => (string)$value]; - } - $this->responseCookies[$name] = array_replace($this->defaults, $value); - } - - /** - * Convert to `Set-Cookie` headers - * - * @return string[] - */ - public function toHeaders() - { - $headers = []; - foreach ($this->responseCookies as $name => $properties) { - $headers[] = $this->toHeader($name, $properties); - } - - return $headers; - } - - /** - * Convert to `Set-Cookie` header - * - * @param string $name Cookie name - * @param array $properties Cookie properties - * - * @return string - */ - protected function toHeader($name, array $properties) - { - $result = urlencode($name) . '=' . urlencode($properties['value']); - - if (isset($properties['domain'])) { - $result .= '; domain=' . $properties['domain']; - } - - if (isset($properties['path'])) { - $result .= '; path=' . $properties['path']; - } - - if (isset($properties['expires'])) { - if (is_string($properties['expires'])) { - $timestamp = strtotime($properties['expires']); - } else { - $timestamp = (int)$properties['expires']; - } - if ($timestamp !== 0) { - $result .= '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp); - } - } - - if (isset($properties['secure']) && $properties['secure']) { - $result .= '; secure'; - } - - if (isset($properties['hostonly']) && $properties['hostonly']) { - $result .= '; HostOnly'; - } - - if (isset($properties['httponly']) && $properties['httponly']) { - $result .= '; HttpOnly'; - } - - return $result; - } - - /** - * Parse HTTP request `Cookie:` header and extract - * into a PHP associative array. - * - * @param string|string[] $header The raw HTTP request `Cookie:` header - * - * @return array Associative array of cookie names and values - * - * @throws InvalidArgumentException if the cookie data cannot be parsed - */ - public static function parseHeader($header) - { - if (is_array($header) === true) { - $header = isset($header[0]) ? $header[0] : ''; - } - - if (is_string($header) === false) { - throw new InvalidArgumentException('Cannot parse Cookie data. Header value must be a string.'); - } - - $header = rtrim($header, "\r\n"); - $pieces = preg_split('@[;]\s*@', $header); - $cookies = []; - - foreach ($pieces as $cookie) { - $cookie = explode('=', $cookie, 2); - - if (count($cookie) === 2) { - $key = urldecode($cookie[0]); - $value = urldecode($cookie[1]); - - if (!isset($cookies[$key])) { - $cookies[$key] = $value; - } - } - } - - return $cookies; - } -} diff --git a/src/CookiesInterface.php b/src/CookiesInterface.php deleted file mode 100644 index c0d9c98..0000000 --- a/src/CookiesInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - 'HTTP/1.1', - 'REQUEST_METHOD' => 'GET', - 'REQUEST_SCHEME' => $defscheme, - 'SCRIPT_NAME' => '', - 'REQUEST_URI' => '', - 'QUERY_STRING' => '', - 'SERVER_NAME' => 'localhost', - 'SERVER_PORT' => $defport, - 'HTTP_HOST' => 'localhost', - 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', - 'HTTP_USER_AGENT' => 'Slim Framework', - 'REMOTE_ADDR' => '127.0.0.1', - 'REQUEST_TIME' => time(), - 'REQUEST_TIME_FLOAT' => microtime(true), - ], $userData); - } -} diff --git a/src/Headers.php b/src/Headers.php deleted file mode 100644 index 9d8c299..0000000 --- a/src/Headers.php +++ /dev/null @@ -1,240 +0,0 @@ - 1, - 'CONTENT_LENGTH' => 1, - 'PHP_AUTH_USER' => 1, - 'PHP_AUTH_PW' => 1, - 'PHP_AUTH_DIGEST' => 1, - 'AUTH_TYPE' => 1, - ]; - - /** - * Create new headers collection with data extracted from - * the PHP global environment - * - * @param array $globals Global server variables - * - * @return self - */ - public static function createFromGlobals(array $globals) - { - $data = []; - $globals = self::determineAuthorization($globals); - foreach ($globals as $key => $value) { - $key = strtoupper($key); - if (isset(static::$special[$key]) || strpos($key, 'HTTP_') === 0) { - if ($key !== 'HTTP_CONTENT_LENGTH') { - $data[self::reconstructOriginalKey($key)] = $value; - } - } - } - - return new static($data); - } - - /** - * If HTTP_AUTHORIZATION does not exist tries to get it from - * getallheaders() when available. - * - * @param array $globals The Slim application Environment - * - * @return array - */ - - public static function determineAuthorization(array $globals) - { - $authorization = isset($globals['HTTP_AUTHORIZATION']) ? $globals['HTTP_AUTHORIZATION'] : null; - - if (empty($authorization) && is_callable('getallheaders')) { - $headers = getallheaders(); - $headers = array_change_key_case($headers, CASE_LOWER); - if (isset($headers['authorization'])) { - $globals['HTTP_AUTHORIZATION'] = $headers['authorization']; - } - } - - return $globals; - } - - /** - * Return array of HTTP header names and values. - * This method returns the _original_ header name - * as specified by the end user. - * - * @return array - */ - public function all() - { - $all = parent::all(); - $out = []; - foreach ($all as $key => $props) { - $out[$props['originalKey']] = $props['value']; - } - - return $out; - } - - /** - * Set HTTP header value - * - * This method sets a header value. It replaces - * any values that may already exist for the header name. - * - * @param string $key The case-insensitive header name - * @param string $value The header value - */ - public function set($key, $value) - { - if (!is_array($value)) { - $value = [$value]; - } - parent::set($this->normalizeKey($key), [ - 'value' => $value, - 'originalKey' => $key - ]); - } - - /** - * Get HTTP header value - * - * @param string $key The case-insensitive header name - * @param mixed $default The default value if key does not exist - * - * @return string[] - */ - public function get($key, $default = null) - { - if ($this->has($key)) { - return parent::get($this->normalizeKey($key))['value']; - } - - return $default; - } - - /** - * Get HTTP header key as originally specified - * - * @param string $key The case-insensitive header name - * @param mixed $default The default value if key does not exist - * - * @return string - */ - public function getOriginalKey($key, $default = null) - { - if ($this->has($key)) { - return parent::get($this->normalizeKey($key))['originalKey']; - } - - return $default; - } - - /** - * Add HTTP header value - * - * This method appends a header value. Unlike the set() method, - * this method _appends_ this new value to any values - * that already exist for this header name. - * - * @param string $key The case-insensitive header name - * @param array|string $value The new header value(s) - */ - public function add($key, $value) - { - $oldValues = $this->get($key, []); - $newValues = is_array($value) ? $value : [$value]; - $this->set($key, array_merge($oldValues, array_values($newValues))); - } - - /** - * Does this collection have a given header? - * - * @param string $key The case-insensitive header name - * - * @return bool - */ - public function has($key) - { - return parent::has($this->normalizeKey($key)); - } - - /** - * Remove header from collection - * - * @param string $key The case-insensitive header name - */ - public function remove($key) - { - parent::remove($this->normalizeKey($key)); - } - - /** - * Normalize header name - * - * This method transforms header names into a - * normalized form. This is how we enable case-insensitive - * header names in the other methods in this class. - * - * @param string $key The case-insensitive header name - * - * @return string Normalized header name - */ - public function normalizeKey($key) - { - $key = strtr(strtolower($key), '_', '-'); - if (strpos($key, 'http-') === 0) { - $key = substr($key, 5); - } - - return $key; - } - - /** - * Reconstruct original header name - * - * This method takes an HTTP header name from the Environment - * and returns it as it was probably formatted by the actual client. - * - * @param string $key An HTTP header key from the $_SERVER global variable - * - * @return string The reconstructed key - * - * @example CONTENT_TYPE => Content-Type - * @example HTTP_USER_AGENT => User-Agent - */ - private static function reconstructOriginalKey($key) - { - if (strpos($key, 'HTTP_') === 0) { - $key = substr($key, 5); - } - return strtr(ucwords(strtr(strtolower($key), '_', ' ')), ' ', '-'); - } -} diff --git a/src/HeadersInterface.php b/src/HeadersInterface.php deleted file mode 100644 index 05fb109..0000000 --- a/src/HeadersInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - true, - '1.1' => true, - '2.0' => true, - '2' => true, - ]; - - /** - * Headers - * - * @var \Slim\Http\HeadersInterface - */ - protected $headers; - - /** - * Body object - * - * @var \Psr\Http\Message\StreamInterface - */ - protected $body; - - - /** - * Disable magic setter to ensure immutability - */ - public function __set($name, $value) - { - // Do nothing - } - - /******************************************************************************* - * Protocol - ******************************************************************************/ - - /** - * Retrieves the HTTP protocol version as a string. - * - * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). - * - * @return string HTTP protocol version. - */ - public function getProtocolVersion() - { - return $this->protocolVersion; - } - - /** - * Return an instance with the specified HTTP protocol version. - * - * The version string MUST contain only the HTTP version number (e.g., - * "1.1", "1.0"). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new protocol version. - * - * @param string $version HTTP protocol version - * @return static - * @throws InvalidArgumentException if the http version is an invalid number - */ - public function withProtocolVersion($version) - { - if (!isset(self::$validProtocolVersions[$version])) { - throw new InvalidArgumentException( - 'Invalid HTTP version. Must be one of: ' - . implode(', ', array_keys(self::$validProtocolVersions)) - ); - } - $clone = clone $this; - $clone->protocolVersion = $version; - - return $clone; - } - - /******************************************************************************* - * Headers - ******************************************************************************/ - - /** - * Retrieves all message header values. - * - * The keys represent the header name as it will be sent over the wire, and - * each value is an array of strings associated with the header. - * - * // Represent the headers as a string - * foreach ($message->getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return array Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getHeaders() - { - return $this->headers->all(); - } - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($name) - { - return $this->headers->has($name); - } - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name. - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array. - */ - public function getHeader($name) - { - return $this->headers->get($name, []); - } - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name. - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string. - */ - public function getHeaderLine($name) - { - return implode(',', $this->headers->get($name, [])); - } - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withHeader($name, $value) - { - $this->validateHeaderName($name); - $this->validateHeaderValue($value); - - $clone = clone $this; - $clone->headers->set($name, $value); - - return $clone; - } - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withAddedHeader($name, $value) - { - $this->validateHeaderName($name); - $this->validateHeaderValue($value); - - $clone = clone $this; - $clone->headers->add($name, $value); - - return $clone; - } - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove. - * @return static - */ - public function withoutHeader($name) - { - $clone = clone $this; - $clone->headers->remove($name); - - return $clone; - } - - /** - * @param string $name - * @throws \InvalidArgumentException - */ - protected function validateHeaderName($name) - { - if (!is_string($name) || empty($name)) { - throw new InvalidArgumentException('Header names must be a non empty strings'); - } - - if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) { - throw new InvalidArgumentException("'$name' is not valid header name"); - } - } - - /** - * @param string|string[] $value - * @throws \InvalidArgumentException - */ - protected function validateHeaderValue($value) - { - if (!is_array($value)) { - $value = [$value]; - } elseif (empty($value)) { - throw new InvalidArgumentException('Header values must be non empty strings'); - } - - foreach ($value as $v) { - if (!is_string($v) && ! is_numeric($v)) { - throw new InvalidArgumentException('Header values must be strings or numeric'); - } - - $v = (string) $v; - if (preg_match("#(?:(?:(?body; - } - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * 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 - * new body stream. - * - * @param StreamInterface $body Body. - * @return static - * @throws \InvalidArgumentException When the body is not valid. - */ - public function withBody(StreamInterface $body) - { - // TODO: Test for invalid body? - $clone = clone $this; - $clone->body = $body; - - return $clone; - } -} diff --git a/src/Request.php b/src/Request.php deleted file mode 100644 index 9144fe3..0000000 --- a/src/Request.php +++ /dev/null @@ -1,1148 +0,0 @@ -get('Cookie', [])); - $serverParams = $globals; - $body = new RequestBody(); - $uploadedFiles = UploadedFile::createFromGlobals($globals); - - $request = new static($method, $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - if ($method === 'POST' && - in_array($request->getMediaType(), ['application/x-www-form-urlencoded', 'multipart/form-data']) - ) { - // parsed body must be $_POST - $request = $request->withParsedBody($_POST); - } - return $request; - } - - /** - * Create new HTTP request. - * - * Adds a host header when none was provided and a host is defined in uri. - * - * @param string $method The request method - * @param UriInterface $uri The request URI object - * @param HeadersInterface $headers The request headers collection - * @param array $cookies The request cookies collection - * @param array $serverParams The server environment variables - * @param StreamInterface $body The request body object - * @param array $uploadedFiles The request uploadedFiles collection - * @throws InvalidArgumentException on invalid HTTP method - */ - public function __construct( - $method, - UriInterface $uri, - HeadersInterface $headers, - array $cookies, - array $serverParams, - StreamInterface $body, - array $uploadedFiles = [] - ) { - $this->method = $this->filterMethod($method); - $this->uri = $uri; - $this->headers = $headers; - $this->cookies = $cookies; - $this->serverParams = $serverParams; - $this->attributes = new Collection(); - $this->body = $body; - $this->uploadedFiles = $uploadedFiles; - - if (isset($serverParams['SERVER_PROTOCOL'])) { - $this->protocolVersion = str_replace('HTTP/', '', $serverParams['SERVER_PROTOCOL']); - } - - if (!$this->headers->has('Host') || $this->uri->getHost() !== '') { - $this->headers->set('Host', $this->uri->getHost()); - } - - $this->registerMediaTypeParser('application/json', function ($input) { - $result = json_decode($input, true); - if (!is_array($result)) { - return null; - } - return $result; - }); - - $this->registerMediaTypeParser('application/xml', function ($input) { - $backup = libxml_disable_entity_loader(true); - $backup_errors = libxml_use_internal_errors(true); - $result = simplexml_load_string($input); - libxml_disable_entity_loader($backup); - libxml_clear_errors(); - libxml_use_internal_errors($backup_errors); - if ($result === false) { - return null; - } - return $result; - }); - - $this->registerMediaTypeParser('text/xml', function ($input) { - $backup = libxml_disable_entity_loader(true); - $backup_errors = libxml_use_internal_errors(true); - $result = simplexml_load_string($input); - libxml_disable_entity_loader($backup); - libxml_clear_errors(); - libxml_use_internal_errors($backup_errors); - if ($result === false) { - return null; - } - return $result; - }); - - $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) { - parse_str($input, $data); - return $data; - }); - } - - /** - * This method is applied to the cloned object - * after PHP performs an initial shallow-copy. This - * method completes a deep-copy by creating new objects - * for the cloned object's internal reference pointers. - */ - public function __clone() - { - $this->headers = clone $this->headers; - $this->attributes = clone $this->attributes; - $this->body = clone $this->body; - } - - /******************************************************************************* - * Method - ******************************************************************************/ - - /** - * Retrieves the HTTP method of the request. - * - * @return string Returns the request method. - */ - public function getMethod() - { - return $this->method; - } - - /** - * Return an instance with the provided HTTP method. - * - * While HTTP method names are typically all uppercase characters, HTTP - * method names are case-sensitive and thus implementations SHOULD NOT - * modify the given string. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request method. - * - * @param string $method Case-sensitive method. - * @return static - * @throws \InvalidArgumentException for invalid HTTP methods. - */ - public function withMethod($method) - { - $method = $this->filterMethod($method); - $clone = clone $this; - $clone->method = $method; - - return $clone; - } - - /** - * Validate the HTTP method - * - * @param null|string $method - * @return string - * @throws \InvalidArgumentException on invalid HTTP method. - */ - protected function filterMethod($method) - { - if ($method === null) { - return ''; - } - - if (!is_string($method)) { - throw new InvalidArgumentException(sprintf( - 'Unsupported HTTP method; must be a string, received %s', - (is_object($method) ? get_class($method) : gettype($method)) - )); - } - - if (preg_match("/^[!#$%&'*+.^_`|~0-9a-z-]+$/i", $method) !== 1) { - throw new InvalidArgumentException(sprintf( - 'Unsupported HTTP method "%s" provided', - $method - )); - } - - return $method; - } - - /** - * Does this request use a given method? - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $method HTTP method - * @return bool - */ - public function isMethod($method) - { - return $this->getMethod() === $method; - } - - /** - * Is this a GET request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isGet() - { - return $this->isMethod('GET'); - } - - /** - * Is this a POST request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isPost() - { - return $this->isMethod('POST'); - } - - /** - * Is this a PUT request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isPut() - { - return $this->isMethod('PUT'); - } - - /** - * Is this a PATCH request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isPatch() - { - return $this->isMethod('PATCH'); - } - - /** - * Is this a DELETE request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isDelete() - { - return $this->isMethod('DELETE'); - } - - /** - * Is this a HEAD request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isHead() - { - return $this->isMethod('HEAD'); - } - - /** - * Is this a OPTIONS request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isOptions() - { - return $this->isMethod('OPTIONS'); - } - - /** - * Is this an XHR request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isXhr() - { - return $this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; - } - - /******************************************************************************* - * URI - ******************************************************************************/ - - /** - * Retrieves the message's request target. - * - * Retrieves the message's request-target either as it will appear (for - * clients), as it appeared at request (for servers), or as it was - * specified for the instance (see withRequestTarget()). - * - * In most cases, this will be the origin-form of the composed URI, - * unless a value was provided to the concrete implementation (see - * withRequestTarget() below). - * - * If no URI is available, and no request-target has been specifically - * provided, this method MUST return the string "/". - * - * @return string - */ - public function getRequestTarget() - { - if ($this->requestTarget) { - return $this->requestTarget; - } - - if ($this->uri === null) { - return '/'; - } - - $path = $this->uri->getPath(); - $path = '/' . ltrim($path, '/'); - - $query = $this->uri->getQuery(); - if ($query) { - $path .= '?' . $query; - } - $this->requestTarget = $path; - - return $this->requestTarget; - } - - /** - * Return an instance with the specific request-target. - * - * If the request needs a non-origin-form request-target — e.g., for - * specifying an absolute-form, authority-form, or asterisk-form — - * this method may be used to create an instance with the specified - * request-target, verbatim. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request target. - * - * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various - * request-target forms allowed in request messages) - * @param mixed $requestTarget - * @return static - * @throws InvalidArgumentException if the request target is invalid - */ - public function withRequestTarget($requestTarget) - { - if (preg_match('#\s#', $requestTarget)) { - throw new InvalidArgumentException( - 'Invalid request target provided; must be a string and cannot contain whitespace' - ); - } - $clone = clone $this; - $clone->requestTarget = $requestTarget; - - return $clone; - } - - /** - * Retrieves the URI instance. - * - * This method MUST return a UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * @return UriInterface Returns a UriInterface instance - * representing the URI of the request. - */ - public function getUri() - { - return $this->uri; - } - - /** - * Returns an instance with the provided URI. - * - * This method MUST update the Host header of the returned request by - * default if the URI contains a host component. If the URI does not - * contain a host component, any pre-existing Host header MUST be carried - * over to the returned request. - * - * You can opt-in to preserving the original state of the Host header by - * setting `$preserveHost` to `true`. When `$preserveHost` is set to - * `true`, this method interacts with the Host header in the following ways: - * - * - If the the Host header is missing or empty, and the new URI contains - * a host component, this method MUST update the Host header in the returned - * request. - * - If the Host header is missing or empty, and the new URI does not contain a - * host component, this method MUST NOT update the Host header in the returned - * request. - * - If a Host header is present and non-empty, this method MUST NOT update - * the Host header in the returned request. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * @param UriInterface $uri New request URI to use. - * @param bool $preserveHost Preserve the original state of the Host header. - * @return static - */ - public function withUri(UriInterface $uri, $preserveHost = false) - { - $clone = clone $this; - $clone->uri = $uri; - - if (!$preserveHost) { - if ($uri->getHost() !== '') { - $clone->headers->set('Host', $uri->getHost()); - } - } else { - if ($uri->getHost() !== '' && (!$this->hasHeader('Host') || $this->getHeaderLine('Host') === '')) { - $clone->headers->set('Host', $uri->getHost()); - } - } - - return $clone; - } - - /** - * Get request content type. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string|null The request content type, if known - */ - public function getContentType() - { - $result = $this->getHeader('Content-Type'); - - return $result ? $result[0] : null; - } - - /** - * Get request media type, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string|null The request media type, minus content-type params - */ - public function getMediaType() - { - $contentType = $this->getContentType(); - if ($contentType) { - $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); - - return strtolower($contentTypeParts[0]); - } - - return null; - } - - /** - * Get request media type params, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return array - */ - public function getMediaTypeParams() - { - $contentType = $this->getContentType(); - $contentTypeParams = []; - if ($contentType) { - $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); - $contentTypePartsLength = count($contentTypeParts); - for ($i = 1; $i < $contentTypePartsLength; $i++) { - $paramParts = explode('=', $contentTypeParts[$i]); - $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; - } - } - - return $contentTypeParams; - } - - /** - * Get request content character set, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string|null - */ - public function getContentCharset() - { - $mediaTypeParams = $this->getMediaTypeParams(); - if (isset($mediaTypeParams['charset'])) { - return $mediaTypeParams['charset']; - } - - return null; - } - - /** - * Get request content length, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return int|null - */ - public function getContentLength() - { - $result = $this->headers->get('Content-Length'); - - return $result ? (int)$result[0] : null; - } - - /******************************************************************************* - * Cookies - ******************************************************************************/ - - /** - * 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->cookies; - } - - /** - * Fetch cookie value from cookies sent by the client to the server. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $key The attribute name. - * @param mixed $default Default value to return if the attribute does not exist. - * - * @return mixed - */ - public function getCookieParam($key, $default = null) - { - $cookies = $this->getCookieParams(); - $result = $default; - if (isset($cookies[$key])) { - $result = $cookies[$key]; - } - - return $result; - } - - /** - * Return an 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 NOT update the related Cookie header of the request - * instance, nor related 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 an instance that has the - * updated cookie values. - * - * @param array $cookies Array of key/value pairs representing cookies. - * @return static - */ - public function withCookieParams(array $cookies) - { - $clone = clone $this; - $clone->cookies = $cookies; - - return $clone; - } - - /******************************************************************************* - * Query Params - ******************************************************************************/ - - /** - * Retrieve query string arguments. - * - * Retrieves the deserialized query string arguments, if any. - * - * Note: the query params might not be in sync with the URI or server - * params. If you need to ensure you are only getting the original - * values, you may need to parse the query string from `getUri()->getQuery()` - * or from the `QUERY_STRING` server param. - * - * @return array - */ - public function getQueryParams() - { - if (is_array($this->queryParams)) { - return $this->queryParams; - } - - if ($this->uri === null) { - return []; - } - - parse_str($this->uri->getQuery(), $this->queryParams); // <-- URL decodes data - - return $this->queryParams; - } - - /** - * Return an 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 URI 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 an instance that has the - * updated query string arguments. - * - * @param array $query Array of query string arguments, typically from - * $_GET. - * @return static - */ - public function withQueryParams(array $query) - { - $clone = clone $this; - $clone->queryParams = $query; - - return $clone; - } - - /******************************************************************************* - * File Params - ******************************************************************************/ - - /** - * Retrieve normalized file upload data. - * - * This method returns upload metadata in a normalized tree, with each leaf - * an instance of Psr\Http\Message\UploadedFileInterface. - * - * These values MAY be prepared from $_FILES or the message body during - * instantiation, or MAY be injected via withUploadedFiles(). - * - * @return array An array tree of UploadedFileInterface instances; an empty - * array MUST be returned if no data is present. - */ - public function getUploadedFiles() - { - return $this->uploadedFiles; - } - - /** - * Create a new instance with the specified uploaded files. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param array $uploadedFiles An array tree of UploadedFileInterface instances. - * @return static - * @throws \InvalidArgumentException if an invalid structure is provided. - */ - public function withUploadedFiles(array $uploadedFiles) - { - $clone = clone $this; - $clone->uploadedFiles = $uploadedFiles; - - return $clone; - } - - /******************************************************************************* - * Server Params - ******************************************************************************/ - - /** - * 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 a server parameter. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $key - * @param mixed $default - * @return mixed - */ - public function getServerParam($key, $default = null) - { - $serverParams = $this->getServerParams(); - - return isset($serverParams[$key]) ? $serverParams[$key] : $default; - } - - /******************************************************************************* - * Attributes - ******************************************************************************/ - - /** - * 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->all(); - } - - /** - * 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) - { - return $this->attributes->get($name, $default); - } - - /** - * Return an 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 an instance that has the - * updated attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $value The value of the attribute. - * @return static - */ - public function withAttribute($name, $value) - { - $clone = clone $this; - $clone->attributes->set($name, $value); - - return $clone; - } - - /** - * Create a new instance with the specified derived request attributes. - * - * Note: This method is not part of the PSR-7 standard. - * - * This method allows setting all new derived request attributes 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 attributes. - * - * @param array $attributes New attributes - * @return static - */ - public function withAttributes(array $attributes) - { - $clone = clone $this; - $clone->attributes = new Collection($attributes); - - return $clone; - } - - /** - * Return an 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 an instance that removes - * the attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @return static - */ - public function withoutAttribute($name) - { - $clone = clone $this; - $clone->attributes->remove($name); - - return $clone; - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * 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. - * @throws RuntimeException if the request body media type parser returns an invalid value - */ - public function getParsedBody() - { - if ($this->bodyParsed !== false) { - return $this->bodyParsed; - } - - if (!$this->body) { - return null; - } - - $mediaType = $this->getMediaType(); - - // look for a media type with a structured syntax suffix (RFC 6839) - $parts = explode('+', $mediaType); - if (count($parts) >= 2) { - $mediaType = 'application/' . $parts[count($parts)-1]; - } - - if (isset($this->bodyParsers[$mediaType]) === true) { - $body = (string)$this->getBody(); - $parsed = $this->bodyParsers[$mediaType]($body); - - if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) { - throw new RuntimeException( - 'Request body media type parser return value must be an array, an object, or null' - ); - } - $this->bodyParsed = $parsed; - return $this->bodyParsed; - } - - return null; - } - - /** - * Return an 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 an 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 static - * @throws \InvalidArgumentException if an unsupported argument type is - * provided. - */ - public function withParsedBody($data) - { - if (!is_null($data) && !is_object($data) && !is_array($data)) { - throw new InvalidArgumentException('Parsed body value must be an array, an object, or null'); - } - - $clone = clone $this; - $clone->bodyParsed = $data; - - return $clone; - } - - /** - * Force Body to be parsed again. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return $this - */ - public function reparseBody() - { - $this->bodyParsed = false; - - return $this; - } - - /** - * Register media type parser. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $mediaType A HTTP media type (excluding content-type - * params). - * @param callable $callable A callable that returns parsed contents for - * media type. - */ - public function registerMediaTypeParser($mediaType, callable $callable) - { - if ($callable instanceof Closure) { - $callable = $callable->bindTo($this); - } - $this->bodyParsers[$mediaType] = $callable; - } - - /******************************************************************************* - * Parameters (e.g., POST and GET data) - ******************************************************************************/ - - /** - * Fetch request parameter value from body or query string (in that order). - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $key The parameter key. - * @param string $default The default value. - * - * @return mixed The parameter value. - */ - public function getParam($key, $default = null) - { - $postParams = $this->getParsedBody(); - $getParams = $this->getQueryParams(); - $result = $default; - if (is_array($postParams) && isset($postParams[$key])) { - $result = $postParams[$key]; - } elseif (is_object($postParams) && property_exists($postParams, $key)) { - $result = $postParams->$key; - } elseif (isset($getParams[$key])) { - $result = $getParams[$key]; - } - - return $result; - } - - /** - * Fetch parameter value from request body. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $key - * @param mixed $default - * - * @return mixed - */ - public function getParsedBodyParam($key, $default = null) - { - $postParams = $this->getParsedBody(); - $result = $default; - if (is_array($postParams) && isset($postParams[$key])) { - $result = $postParams[$key]; - } elseif (is_object($postParams) && property_exists($postParams, $key)) { - $result = $postParams->$key; - } - - return $result; - } - - /** - * Fetch parameter value from query string. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $key - * @param mixed $default - * - * @return mixed - */ - public function getQueryParam($key, $default = null) - { - $getParams = $this->getQueryParams(); - $result = $default; - if (isset($getParams[$key])) { - $result = $getParams[$key]; - } - - return $result; - } - - /** - * Fetch associative array of body and query string parameters. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return array - */ - public function getParams() - { - $params = $this->getQueryParams(); - $postParams = $this->getParsedBody(); - if ($postParams) { - $params = array_merge($params, (array)$postParams); - } - - return $params; - } -} diff --git a/src/RequestBody.php b/src/RequestBody.php deleted file mode 100644 index 744bbc7..0000000 --- a/src/RequestBody.php +++ /dev/null @@ -1,27 +0,0 @@ - 'Continue', - 101 => 'Switching Protocols', - 102 => 'Processing', - //Successful 2xx - 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 3xx - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => '(Unused)', - 307 => 'Temporary Redirect', - 308 => 'Permanent Redirect', - //Client Error 4xx - 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 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 418 => 'I\'m a teapot', - 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', - 444 => 'Connection Closed Without Response', - 451 => 'Unavailable For Legal Reasons', - 499 => 'Client Closed Request', - //Server Error 5xx - 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', - ]; - - /** - * EOL characters used for HTTP response. - * - * @var string - */ - const EOL = "\r\n"; - - /** - * Create new HTTP response. - * - * @param int $status The response status code. - * @param HeadersInterface|null $headers The response headers. - * @param StreamInterface|null $body The response body. - */ - public function __construct($status = 200, HeadersInterface $headers = null, StreamInterface $body = null) - { - $this->status = $this->filterStatus($status); - $this->headers = $headers ? $headers : new Headers(); - $this->body = $body ? $body : new Body(fopen('php://temp', 'r+')); - } - - /** - * This method is applied to the cloned object - * after PHP performs an initial shallow-copy. This - * method completes a deep-copy by creating new objects - * for the cloned object's internal reference pointers. - */ - public function __clone() - { - $this->headers = clone $this->headers; - } - - /******************************************************************************* - * Status - ******************************************************************************/ - - /** - * Gets the response status code. - * - * The status code is a 3-digit integer result code of the server's attempt - * to understand and satisfy the request. - * - * @return int Status code. - */ - public function getStatusCode() - { - return $this->status; - } - - /** - * Return an instance with the specified status code and, optionally, reason phrase. - * - * 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 an 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 int $code The 3-digit integer result code to set. - * @param 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 static - * @throws \InvalidArgumentException For invalid status code arguments. - */ - public function withStatus($code, $reasonPhrase = '') - { - $code = $this->filterStatus($code); - - if (!is_string($reasonPhrase) && !method_exists($reasonPhrase, '__toString')) { - throw new InvalidArgumentException('ReasonPhrase must be a string'); - } - - $clone = clone $this; - $clone->status = $code; - if ($reasonPhrase === '' && isset(static::$messages[$code])) { - $reasonPhrase = static::$messages[$code]; - } - - if ($reasonPhrase === '') { - throw new InvalidArgumentException('ReasonPhrase must be supplied for this code'); - } - - $clone->reasonPhrase = $reasonPhrase; - - return $clone; - } - - /** - * Filter HTTP status code. - * - * @param int $status HTTP status code. - * @return int - * @throws \InvalidArgumentException If an invalid HTTP status code is provided. - */ - protected function filterStatus($status) - { - if (!is_integer($status) || $status<100 || $status>599) { - throw new InvalidArgumentException('Invalid HTTP status code'); - } - - return $status; - } - - /** - * Gets the response reason phrase associated with 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 Reason phrase; must return an empty string if none present. - */ - public function getReasonPhrase() - { - if ($this->reasonPhrase) { - return $this->reasonPhrase; - } - if (isset(static::$messages[$this->status])) { - return static::$messages[$this->status]; - } - return ''; - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * Write data to the response body. - * - * Note: This method is not part of the PSR-7 standard. - * - * Proxies to the underlying stream and writes the provided data to it. - * - * @param string $data - * @return $this - */ - public function write($data) - { - $this->getBody()->write($data); - - return $this; - } - - /******************************************************************************* - * Response Helpers - ******************************************************************************/ - - /** - * Redirect. - * - * Note: This method is not part of the PSR-7 standard. - * - * This method prepares the response object to return an HTTP Redirect - * response to the client. - * - * @param string|UriInterface $url The redirect destination. - * @param int|null $status The redirect HTTP status code. - * @return static - */ - public function withRedirect($url, $status = null) - { - $responseWithRedirect = $this->withHeader('Location', (string)$url); - - if (is_null($status) && $this->getStatusCode() === 200) { - $status = 302; - } - - if (!is_null($status)) { - return $responseWithRedirect->withStatus($status); - } - - return $responseWithRedirect; - } - - /** - * Json. - * - * Note: This method is not part of the PSR-7 standard. - * - * This method prepares the response object to return an HTTP Json - * response to the client. - * - * @param mixed $data The data - * @param int $status The HTTP status code. - * @param int $encodingOptions Json encoding options - * @throws \RuntimeException - * @return static - */ - public function withJson($data, $status = null, $encodingOptions = 0) - { - $response = $this->withBody(new Body(fopen('php://temp', 'r+'))); - $response->body->write($json = json_encode($data, $encodingOptions)); - - // Ensure that the json encoding passed successfully - if ($json === false) { - throw new \RuntimeException(json_last_error_msg(), json_last_error()); - } - - $responseWithJson = $response->withHeader('Content-Type', 'application/json;charset=utf-8'); - if (isset($status)) { - return $responseWithJson->withStatus($status); - } - return $responseWithJson; - } - - /** - * Is this response empty? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isEmpty() - { - return in_array($this->getStatusCode(), [204, 205, 304]); - } - - /** - * Is this response informational? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isInformational() - { - return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; - } - - /** - * Is this response OK? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isOk() - { - return $this->getStatusCode() === 200; - } - - /** - * Is this response successful? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isSuccessful() - { - return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; - } - - /** - * Is this response a redirect? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isRedirect() - { - return in_array($this->getStatusCode(), [301, 302, 303, 307, 308]); - } - - /** - * Is this response a redirection? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isRedirection() - { - return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; - } - - /** - * Is this response forbidden? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - * @api - */ - public function isForbidden() - { - return $this->getStatusCode() === 403; - } - - /** - * Is this response not Found? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isNotFound() - { - return $this->getStatusCode() === 404; - } - - /** - * Is this response a client error? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isClientError() - { - return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; - } - - /** - * Is this response a server error? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isServerError() - { - return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; - } - - /** - * Convert response to string. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string - */ - public function __toString() - { - $output = sprintf( - 'HTTP/%s %s %s', - $this->getProtocolVersion(), - $this->getStatusCode(), - $this->getReasonPhrase() - ); - $output .= Response::EOL; - foreach ($this->getHeaders() as $name => $values) { - $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)) . Response::EOL; - } - $output .= Response::EOL; - $output .= (string)$this->getBody(); - - return $output; - } -} diff --git a/src/Stream.php b/src/Stream.php deleted file mode 100644 index f49d176..0000000 --- a/src/Stream.php +++ /dev/null @@ -1,450 +0,0 @@ - ['r', 'r+', 'w+', 'a+', 'x+', 'c+'], - 'writable' => ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'], - ]; - - /** - * The underlying stream resource - * - * @var resource|null - */ - protected $stream; - - /** - * Stream metadata - * - * @var array|null - */ - protected $meta; - - /** - * Is this stream readable? - * - * @var bool|null - */ - protected $readable; - - /** - * Is this stream writable? - * - * @var bool|null - */ - protected $writable; - - /** - * Is this stream seekable? - * - * @var bool|null - */ - protected $seekable; - - /** - * The size of the stream if known - * - * @var null|int - */ - protected $size; - - /** - * Is this stream a pipe? - * - * @var bool|null - */ - protected $isPipe; - - /** - * Create a new Stream. - * - * @param resource $stream A PHP resource handle. - * - * @throws InvalidArgumentException If argument is not a resource. - */ - public function __construct($stream) - { - $this->attach($stream); - } - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * - * @param string $key Specific metadata to retrieve. - * - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. - */ - public function getMetadata($key = null) - { - $this->meta = stream_get_meta_data($this->stream); - if (is_null($key) === true) { - return $this->meta; - } - - return isset($this->meta[$key]) ? $this->meta[$key] : null; - } - - /** - * Is a resource attached to this stream? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - protected function isAttached() - { - return is_resource($this->stream); - } - - /** - * Attach new resource to this object. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param resource $newStream A PHP resource handle. - * - * @throws InvalidArgumentException If argument is not a valid PHP resource. - */ - protected function attach($newStream) - { - if (is_resource($newStream) === false) { - throw new InvalidArgumentException(__METHOD__ . ' argument must be a valid PHP resource'); - } - - if ($this->isAttached() === true) { - $this->detach(); - } - - $this->stream = $newStream; - } - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach() - { - $oldResource = $this->stream; - $this->stream = null; - $this->meta = null; - $this->readable = null; - $this->writable = null; - $this->seekable = null; - $this->size = null; - $this->isPipe = null; - - return $oldResource; - } - - /** - * Reads all data from the stream into a string, from the beginning to end. - * - * This method MUST attempt to seek to the beginning of the stream before - * reading data and read the stream until the end is reached. - * - * Warning: This could attempt to load a large amount of data into memory. - * - * This method MUST NOT raise an exception in order to conform with PHP's - * string casting operations. - * - * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * @return string - */ - public function __toString() - { - if (!$this->isAttached()) { - return ''; - } - - try { - $this->rewind(); - return $this->getContents(); - } catch (RuntimeException $e) { - return ''; - } - } - - /** - * Closes the stream and any underlying resources. - */ - public function close() - { - if ($this->isAttached() === true) { - if ($this->isPipe()) { - pclose($this->stream); - } else { - fclose($this->stream); - } - } - - $this->detach(); - } - - /** - * Get the size of the stream if known. - * - * @return int|null Returns the size in bytes if known, or null if unknown. - */ - public function getSize() - { - if (!$this->size && $this->isAttached() === true) { - $stats = fstat($this->stream); - $this->size = isset($stats['size']) && !$this->isPipe() ? $stats['size'] : null; - } - - return $this->size; - } - - /** - * Returns the current position of the file read/write pointer - * - * @return int Position of the file pointer - * - * @throws RuntimeException on error. - */ - public function tell() - { - if (!$this->isAttached() || ($position = ftell($this->stream)) === false || $this->isPipe()) { - throw new RuntimeException('Could not get the position of the pointer in stream'); - } - - return $position; - } - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof() - { - return $this->isAttached() ? feof($this->stream) : true; - } - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable() - { - if ($this->readable === null) { - if ($this->isPipe()) { - $this->readable = true; - } else { - $this->readable = false; - if ($this->isAttached()) { - $meta = $this->getMetadata(); - foreach (self::$modes['readable'] as $mode) { - if (strpos($meta['mode'], $mode) === 0) { - $this->readable = true; - break; - } - } - } - } - } - - return $this->readable; - } - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable() - { - if ($this->writable === null) { - $this->writable = false; - if ($this->isAttached()) { - $meta = $this->getMetadata(); - foreach (self::$modes['writable'] as $mode) { - if (strpos($meta['mode'], $mode) === 0) { - $this->writable = true; - break; - } - } - } - } - - return $this->writable; - } - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable() - { - if ($this->seekable === null) { - $this->seekable = false; - if ($this->isAttached()) { - $meta = $this->getMetadata(); - $this->seekable = !$this->isPipe() && $meta['seekable']; - } - } - - return $this->seekable; - } - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * - * @throws RuntimeException on failure. - */ - public function seek($offset, $whence = SEEK_SET) - { - // Note that fseek returns 0 on success! - if (!$this->isSeekable() || fseek($this->stream, $offset, $whence) === -1) { - throw new RuntimeException('Could not seek in stream'); - } - } - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * - * @link http://www.php.net/manual/en/function.fseek.php - * - * @throws RuntimeException on failure. - */ - public function rewind() - { - if (!$this->isSeekable() || rewind($this->stream) === false) { - throw new RuntimeException('Could not rewind stream'); - } - } - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * - * @throws RuntimeException if an error occurs. - */ - public function read($length) - { - if (!$this->isReadable() || ($data = fread($this->stream, $length)) === false) { - throw new RuntimeException('Could not read from stream'); - } - - return $data; - } - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written. - * - * @return int Returns the number of bytes written to the stream. - * - * @throws RuntimeException on failure. - */ - public function write($string) - { - if (!$this->isWritable() || ($written = fwrite($this->stream, $string)) === false) { - throw new RuntimeException('Could not write to stream'); - } - - // reset size so that it will be recalculated on next call to getSize() - $this->size = null; - - return $written; - } - - /** - * Returns the remaining contents in a string - * - * @return string - * - * @throws RuntimeException if unable to read or an error occurs while - * reading. - */ - public function getContents() - { - if (!$this->isReadable() || ($contents = stream_get_contents($this->stream)) === false) { - throw new RuntimeException('Could not get contents of stream'); - } - - return $contents; - } - - /** - * Returns whether or not the stream is a pipe. - * - * @return bool - */ - public function isPipe() - { - if ($this->isPipe === null) { - $this->isPipe = false; - if ($this->isAttached()) { - $mode = fstat($this->stream)['mode']; - $this->isPipe = ($mode & self::FSTAT_MODE_S_IFIFO) !== 0; - } - } - - return $this->isPipe; - } -} diff --git a/src/UploadedFile.php b/src/UploadedFile.php deleted file mode 100644 index d084d46..0000000 --- a/src/UploadedFile.php +++ /dev/null @@ -1,333 +0,0 @@ -has('slim.files')) { - return $env['slim.files']; - } elseif (isset($_FILES)) { - return static::parseUploadedFiles($_FILES); - } - - return []; - } - - /** - * Parse a non-normalized, i.e. $_FILES superglobal, tree of uploaded file data. - * - * @param array $uploadedFiles The non-normalized tree of uploaded file data. - * - * @return array A normalized tree of UploadedFile instances. - */ - private static function parseUploadedFiles(array $uploadedFiles) - { - $parsed = []; - foreach ($uploadedFiles as $field => $uploadedFile) { - if (!isset($uploadedFile['error'])) { - if (is_array($uploadedFile)) { - $parsed[$field] = static::parseUploadedFiles($uploadedFile); - } - continue; - } - - $parsed[$field] = []; - if (!is_array($uploadedFile['error'])) { - $parsed[$field] = new static( - $uploadedFile['tmp_name'], - isset($uploadedFile['name']) ? $uploadedFile['name'] : null, - isset($uploadedFile['type']) ? $uploadedFile['type'] : null, - isset($uploadedFile['size']) ? $uploadedFile['size'] : null, - $uploadedFile['error'], - true - ); - } else { - $subArray = []; - foreach ($uploadedFile['error'] as $fileIdx => $error) { - // normalise subarray and re-parse to move the input's keyname up a level - $subArray[$fileIdx]['name'] = $uploadedFile['name'][$fileIdx]; - $subArray[$fileIdx]['type'] = $uploadedFile['type'][$fileIdx]; - $subArray[$fileIdx]['tmp_name'] = $uploadedFile['tmp_name'][$fileIdx]; - $subArray[$fileIdx]['error'] = $uploadedFile['error'][$fileIdx]; - $subArray[$fileIdx]['size'] = $uploadedFile['size'][$fileIdx]; - - $parsed[$field] = static::parseUploadedFiles($subArray); - } - } - } - - return $parsed; - } - - /** - * Construct a new UploadedFile instance. - * - * @param string $file The full path to the uploaded file provided by the client. - * @param string|null $name The file name. - * @param string|null $type The file media type. - * @param int|null $size The file size in bytes. - * @param int $error The UPLOAD_ERR_XXX code representing the status of the upload. - * @param bool $sapi Indicates if the upload is in a SAPI environment. - */ - public function __construct($file, $name = null, $type = null, $size = null, $error = UPLOAD_ERR_OK, $sapi = false) - { - $this->file = $file; - $this->name = $name; - $this->type = $type; - $this->size = $size; - $this->error = $error; - $this->sapi = $sapi; - } - - /** - * Retrieve a stream representing the uploaded file. - * - * This method MUST return a StreamInterface instance, representing the - * uploaded file. The purpose of this method is to allow utilizing native PHP - * stream functionality to manipulate the file upload, such as - * stream_copy_to_stream() (though the result will need to be decorated in a - * native PHP stream wrapper to work with such functions). - * - * If the moveTo() method has been called previously, this method MUST raise - * an exception. - * - * @return StreamInterface Stream representation of the uploaded file. - * @throws \RuntimeException in cases when no stream is available or can be - * created. - */ - public function getStream() - { - if ($this->moved) { - throw new \RuntimeException(sprintf('Uploaded file %s has already been moved', $this->name)); - } - if ($this->stream === null) { - $this->stream = new Stream(fopen($this->file, 'r')); - } - - return $this->stream; - } - - /** - * Move the uploaded file to a new location. - * - * Use this method as an alternative to move_uploaded_file(). This method is - * guaranteed to work in both SAPI and non-SAPI environments. - * Implementations must determine which environment they are in, and use the - * appropriate method (move_uploaded_file(), rename(), or a stream - * operation) to perform the operation. - * - * $targetPath may be an absolute path, or a relative path. If it is a - * relative path, resolution should be the same as used by PHP's rename() - * function. - * - * The original file or stream MUST be removed on completion. - * - * If this method is called more than once, any subsequent calls MUST raise - * an exception. - * - * When used in an SAPI environment where $_FILES is populated, when writing - * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be - * used to ensure permissions and upload status are verified correctly. - * - * If you wish to move to a stream, use getStream(), as SAPI operations - * cannot guarantee writing to stream destinations. - * - * @see http://php.net/is_uploaded_file - * @see http://php.net/move_uploaded_file - * - * @param string $targetPath Path to which to move the uploaded file. - * - * @throws InvalidArgumentException if the $path specified is invalid. - * @throws RuntimeException on any error during the move operation, or on - * the second or subsequent call to the method. - */ - public function moveTo($targetPath) - { - if ($this->moved) { - throw new RuntimeException('Uploaded file already moved'); - } - - $targetIsStream = strpos($targetPath, '://') > 0; - if (!$targetIsStream && !is_writable(dirname($targetPath))) { - throw new InvalidArgumentException('Upload target path is not writable'); - } - - if ($targetIsStream) { - if (!copy($this->file, $targetPath)) { - throw new RuntimeException(sprintf('Error moving uploaded file %s to %s', $this->name, $targetPath)); - } - if (!unlink($this->file)) { - throw new RuntimeException(sprintf('Error removing uploaded file %s', $this->name)); - } - } elseif ($this->sapi) { - if (!is_uploaded_file($this->file)) { - throw new RuntimeException(sprintf('%s is not a valid uploaded file', $this->file)); - } - - if (!move_uploaded_file($this->file, $targetPath)) { - throw new RuntimeException(sprintf('Error moving uploaded file %s to %s', $this->name, $targetPath)); - } - } else { - if (!rename($this->file, $targetPath)) { - throw new RuntimeException(sprintf('Error moving uploaded file %s to %s', $this->name, $targetPath)); - } - } - - $this->moved = true; - } - - /** - * Retrieve the error associated with the uploaded file. - * - * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants. - * - * If the file was uploaded successfully, this method MUST return - * UPLOAD_ERR_OK. - * - * Implementations SHOULD return the value stored in the "error" key of - * the file in the $_FILES array. - * - * @see http://php.net/manual/en/features.file-upload.errors.php - * - * @return int One of PHP's UPLOAD_ERR_XXX constants. - */ - public function getError() - { - return $this->error; - } - - /** - * Retrieve the filename sent by the client. - * - * Do not trust the value returned by this method. A client could send - * a malicious filename with the intention to corrupt or hack your - * application. - * - * Implementations SHOULD return the value stored in the "name" key of - * the file in the $_FILES array. - * - * @return string|null The filename sent by the client or null if none - * was provided. - */ - public function getClientFilename() - { - return $this->name; - } - - /** - * Retrieve the media type sent by the client. - * - * Do not trust the value returned by this method. A client could send - * a malicious media type with the intention to corrupt or hack your - * application. - * - * Implementations SHOULD return the value stored in the "type" key of - * the file in the $_FILES array. - * - * @return string|null The media type sent by the client or null if none - * was provided. - */ - public function getClientMediaType() - { - return $this->type; - } - - /** - * Retrieve the file size. - * - * Implementations SHOULD return the value stored in the "size" key of - * the file in the $_FILES array if available, as PHP calculates this based - * on the actual size transmitted. - * - * @return int|null The file size in bytes or null if unknown. - */ - public function getSize() - { - return $this->size; - } -} diff --git a/src/Uri.php b/src/Uri.php deleted file mode 100644 index 40ee88c..0000000 --- a/src/Uri.php +++ /dev/null @@ -1,795 +0,0 @@ -scheme = $this->filterScheme($scheme); - $this->host = $this->filterHost($host); - $this->port = $this->filterPort($port); - $this->path = $this->filterPath($path); - $this->query = $this->filterQuery($query); - $this->fragment = $this->filterQuery($fragment); - $this->user = $user; - $this->password = $password; - } - - /** - * Create new Uri from string. - * - * @param string $uri Complete Uri string - * (i.e., https://user:pass@host:443/path?query). - * - * @return self - */ - public static function createFromString($uri) - { - if (!is_string($uri) && !method_exists($uri, '__toString')) { - throw new InvalidArgumentException('Uri must be a string'); - } - - $parts = parse_url($uri); - $scheme = isset($parts['scheme']) ? $parts['scheme'] : ''; - $user = isset($parts['user']) ? $parts['user'] : ''; - $pass = isset($parts['pass']) ? $parts['pass'] : ''; - $host = isset($parts['host']) ? $parts['host'] : ''; - $port = isset($parts['port']) ? $parts['port'] : null; - $path = isset($parts['path']) ? $parts['path'] : ''; - $query = isset($parts['query']) ? $parts['query'] : ''; - $fragment = isset($parts['fragment']) ? $parts['fragment'] : ''; - - return new static($scheme, $host, $port, $path, $query, $fragment, $user, $pass); - } - - /** - * Create new Uri from environment. - * - * @param array $globals The global server variables. - * - * @return self - */ - public static function createFromGlobals(array $globals) - { - $env = new Collection($globals); - - // Scheme - $isSecure = $env->get('HTTPS'); - $scheme = (empty($isSecure) || $isSecure === 'off') ? 'http' : 'https'; - - // Authority: Username and password - $username = $env->get('PHP_AUTH_USER', ''); - $password = $env->get('PHP_AUTH_PW', ''); - - // Authority: Host - if ($env->has('HTTP_HOST')) { - $host = $env->get('HTTP_HOST'); - } else { - $host = $env->get('SERVER_NAME'); - } - - // Authority: Port - $port = (int)$env->get('SERVER_PORT', 80); - if (preg_match('/^(\[[a-fA-F0-9:.]+\])(:\d+)?\z/', $host, $matches)) { - $host = $matches[1]; - - if (isset($matches[2])) { - $port = (int) substr($matches[2], 1); - } - } else { - $pos = strpos($host, ':'); - if ($pos !== false) { - $port = (int) substr($host, $pos + 1); - $host = strstr($host, ':', true); - } - } - - $requestUri = $env->get('REQUEST_URI'); - - // Query string - $queryString = $env->get('QUERY_STRING', ''); - if ($queryString === '') { - $queryString = parse_url('http://example.com' . $env->get('REQUEST_URI'), PHP_URL_QUERY); - } - - // Fragment - $fragment = ''; - - // Build Uri - $uri = new static($scheme, $host, $port, $requestUri, $queryString, $fragment, $username, $password); - - return $uri; - } - - /******************************************************************************** - * Scheme - *******************************************************************************/ - - /** - * Retrieve the scheme component of the URI. - * - * If no scheme is present, this method MUST return an empty string. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.1. - * - * The trailing ":" character is not part of the scheme and MUST NOT be - * added. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.1 - * @return string The URI scheme. - */ - public function getScheme() - { - return $this->scheme; - } - - /** - * Return an instance with the specified scheme. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified scheme. - * - * Implementations MUST support the schemes "http" and "https" case - * insensitively, and MAY accommodate other schemes if required. - * - * An empty scheme is equivalent to removing the scheme. - * - * @param string $scheme The scheme to use with the new instance. - * @return self A new instance with the specified scheme. - * @throws \InvalidArgumentException for invalid or unsupported schemes. - */ - public function withScheme($scheme) - { - $scheme = $this->filterScheme($scheme); - $clone = clone $this; - $clone->scheme = $scheme; - - return $clone; - } - - /** - * Filter Uri scheme. - * - * @param string $scheme Raw Uri scheme. - * @return string - * - * @throws InvalidArgumentException If the Uri scheme is not a string. - * @throws InvalidArgumentException If Uri scheme is not "", "https", or "http". - */ - protected function filterScheme($scheme) - { - static $valid = [ - '' => true, - 'https' => true, - 'http' => true, - ]; - - if (!is_string($scheme) && !method_exists($scheme, '__toString')) { - throw new InvalidArgumentException('Uri scheme must be a string'); - } - - $scheme = str_replace('://', '', strtolower($scheme)); - if (!isset($valid[$scheme])) { - throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"'); - } - - return $scheme; - } - - /******************************************************************************** - * Authority - *******************************************************************************/ - - /** - * Retrieve the authority component of the URI. - * - * If no authority information is present, this method MUST return an empty - * string. - * - * The authority syntax of the URI is: - * - *
-     * [user-info@]host[:port]
-     * 
- * - * If the port component is not set or is the standard port for the current - * scheme, it SHOULD NOT be included. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2 - * @return string The URI authority, in "[user-info@]host[:port]" format. - */ - public function getAuthority() - { - $userInfo = $this->getUserInfo(); - $host = $this->getHost(); - $port = $this->getPort(); - - return ($userInfo !== '' ? $userInfo . '@' : '') . $host . ($port !== null ? ':' . $port : ''); - } - - /** - * Retrieve the user information component of the URI. - * - * If no user information is present, this method MUST return an empty - * string. - * - * If a user is present in the URI, this will return that value; - * additionally, if the password is also present, it will be appended to the - * user value, with a colon (":") separating the values. - * - * The trailing "@" character is not part of the user information and MUST - * NOT be added. - * - * @return string The URI user information, in "username[:password]" format. - */ - public function getUserInfo() - { - $info = $this->user; - - if (isset($this->password) && $this->password !== '') { - $info .= ':' . $this->password; - } - - return $info; - } - - /** - * Return an instance with the specified user information. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user information. - * - * Password is optional, but the user information MUST include the - * user; an empty string for the user is equivalent to removing user - * information. - * - * @param string $user The user name to use for authority. - * @param null|string $password The password associated with $user. - * @return self A new instance with the specified user information. - */ - public function withUserInfo($user, $password = null) - { - $clone = clone $this; - $clone->user = $this->filterUserInfo($user); - if ($clone->user !== '') { - $clone->password = isset($password) ? $this->filterUserInfo($password) : ''; - } else { - $clone->password = ''; - } - - return $clone; - } - - /** - * Filters the user info string. - * - * @param string $query The raw uri query string. - * @return string The percent-encoded query string. - */ - protected function filterUserInfo($query) - { - return preg_replace_callback( - '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=]+|%(?![A-Fa-f0-9]{2}))/u', - function ($match) { - return rawurlencode($match[0]); - }, - $query - ); - } - - /** - * Retrieve the host component of the URI. - * - * If no host is present, this method MUST return an empty string. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.2.2. - * - * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 - * @return string The URI host. - */ - public function getHost() - { - return $this->host; - } - - /** - * Return an instance with the specified host. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified host. - * - * An empty host value is equivalent to removing the host. - * - * @param string $host The hostname to use with the new instance. - * @return self A new instance with the specified host. - * @throws \InvalidArgumentException for invalid hostnames. - */ - public function withHost($host) - { - $clone = clone $this; - $clone->host = $this->filterHost($host); - - return $clone; - } - - /** - * Filter Uri host. - * - * If the supplied host is an IPv6 address, then it is converted to a reference - * as per RFC 2373. - * - * @param string $host The host to filter. - * @return string - * @throws \InvalidArgumentException for invalid host names. - */ - protected function filterHost($host) - { - if (!is_string($host) && !method_exists($host, '__toString')) { - throw new InvalidArgumentException('Uri host must be a string'); - } - - if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - $host = '[' . $host . ']'; - } - - return strtolower($host); - } - - /** - * Retrieve the port component of the URI. - * - * If a port is present, and it is non-standard for the current scheme, - * this method MUST return it as an integer. If the port is the standard port - * used with the current scheme, this method SHOULD return null. - * - * If no port is present, and no scheme is present, this method MUST return - * a null value. - * - * If no port is present, but a scheme is present, this method MAY return - * the standard port for that scheme, but SHOULD return null. - * - * @return null|int The URI port. - */ - public function getPort() - { - return $this->port && !$this->hasStandardPort() ? $this->port : null; - } - - /** - * Return an instance with the specified port. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified port. - * - * Implementations MUST raise an exception for ports outside the - * established TCP and UDP port ranges. - * - * A null value provided for the port is equivalent to removing the port - * information. - * - * @param null|int $port The port to use with the new instance; a null value - * removes the port information. - * @return self A new instance with the specified port. - * @throws \InvalidArgumentException for invalid ports. - */ - public function withPort($port) - { - $port = $this->filterPort($port); - $clone = clone $this; - $clone->port = $port; - - return $clone; - } - - /** - * Does this Uri use a standard port? - * - * @return bool - */ - protected function hasStandardPort() - { - return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443); - } - - /** - * Filter Uri port. - * - * @param null|int $port The Uri port number. - * @return null|int - * - * @throws InvalidArgumentException If the port is invalid. - */ - protected function filterPort($port) - { - if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) { - return $port; - } - - throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)'); - } - - /******************************************************************************** - * Path - *******************************************************************************/ - - /** - * Retrieve the path component of the URI. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * Normally, the empty path "" and absolute path "/" are considered equal as - * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically - * do this normalization because in contexts with a trimmed base path, e.g. - * the front controller, this difference becomes significant. It's the task - * of the user to handle both "" and "/". - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.3. - * - * As an example, if the value should include a slash ("/") not intended as - * delimiter between path segments, that value MUST be passed in encoded - * form (e.g., "%2F") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.3 - * @return string The URI path. - */ - public function getPath() - { - return $this->path; - } - - /** - * Return an instance with the specified path. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified path. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * If the path is intended to be domain-relative rather than path relative then - * it must begin with a slash ("/"). Paths not starting with a slash ("/") - * are assumed to be relative to some base path known to the application or - * consumer. - * - * Users can provide both encoded and decoded path characters. - * Implementations ensure the correct encoding as outlined in getPath(). - * - * @param string $path The path to use with the new instance. - * @return self A new instance with the specified path. - * @throws \InvalidArgumentException for invalid paths. - */ - public function withPath($path) - { - if (!is_string($path)) { - throw new InvalidArgumentException('Uri path must be a string'); - } - - $clone = clone $this; - $clone->path = $this->filterPath($path); - - return $clone; - } - - /** - * Filter Uri path. - * - * This method percent-encodes all reserved - * characters in the provided path string. This method - * will NOT double-encode characters that are already - * percent-encoded. - * - * @param string $path The raw uri path. - * @return string The RFC 3986 percent-encoded uri path. - * @link http://www.faqs.org/rfcs/rfc3986.html - */ - protected function filterPath($path) - { - return preg_replace_callback( - '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', - function ($match) { - return rawurlencode($match[0]); - }, - $path - ); - } - - /******************************************************************************** - * Query - *******************************************************************************/ - - /** - * Retrieve the query string of the URI. - * - * If no query string is present, this method MUST return an empty string. - * - * The leading "?" character is not part of the query and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.4. - * - * As an example, if a value in a key/value pair of the query string should - * include an ampersand ("&") not intended as a delimiter between values, - * that value MUST be passed in encoded form (e.g., "%26") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.4 - * @return string The URI query string. - */ - public function getQuery() - { - return $this->query; - } - - /** - * Return an instance with the specified query string. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified query string. - * - * Users can provide both encoded and decoded query characters. - * Implementations ensure the correct encoding as outlined in getQuery(). - * - * An empty query string value is equivalent to removing the query string. - * - * @param string $query The query string to use with the new instance. - * @return self A new instance with the specified query string. - * @throws \InvalidArgumentException for invalid query strings. - */ - public function withQuery($query) - { - if (!is_string($query) && !method_exists($query, '__toString')) { - throw new InvalidArgumentException('Uri query must be a string'); - } - $query = ltrim($query, '?'); - $clone = clone $this; - $clone->query = $this->filterQuery($query); - - return $clone; - } - - /** - * Filters the query string or fragment of a URI. - * - * @param string $query The raw uri query string. - * @return string The percent-encoded query string. - */ - protected function filterQuery($query) - { - return preg_replace_callback( - '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', - function ($match) { - return rawurlencode($match[0]); - }, - $query - ); - } - - /******************************************************************************** - * Fragment - *******************************************************************************/ - - /** - * Retrieve the fragment component of the URI. - * - * If no fragment is present, this method MUST return an empty string. - * - * The leading "#" character is not part of the fragment and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.5. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.5 - * @return string The URI fragment. - */ - public function getFragment() - { - return $this->fragment; - } - - /** - * Return an instance with the specified URI fragment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified URI fragment. - * - * Users can provide both encoded and decoded fragment characters. - * Implementations ensure the correct encoding as outlined in getFragment(). - * - * An empty fragment value is equivalent to removing the fragment. - * - * @param string $fragment The fragment to use with the new instance. - * @return self A new instance with the specified fragment. - */ - public function withFragment($fragment) - { - if (!is_string($fragment) && !method_exists($fragment, '__toString')) { - throw new InvalidArgumentException('Uri fragment must be a string'); - } - $fragment = ltrim($fragment, '#'); - $clone = clone $this; - $clone->fragment = $this->filterQuery($fragment); - - return $clone; - } - - /******************************************************************************** - * Helpers - *******************************************************************************/ - - /** - * Return the string representation as a URI reference. - * - * Depending on which components of the URI are present, the resulting - * string is either a full URI or relative reference according to RFC 3986, - * Section 4.1. The method concatenates the various components of the URI, - * using the appropriate delimiters: - * - * - If a scheme is present, it MUST be suffixed by ":". - * - If an authority is present, it MUST be prefixed by "//". - * - The path can be concatenated without delimiters. But there are two - * cases where the path has to be adjusted to make the URI reference - * valid as PHP does not allow to throw an exception in __toString(): - * - If the path is rootless and an authority is present, the path MUST - * be prefixed by "/". - * - If the path is starting with more than one "/" and no authority is - * present, the starting slashes MUST be reduced to one. - * - If a query is present, it MUST be prefixed by "?". - * - If a fragment is present, it MUST be prefixed by "#". - * - * @see http://tools.ietf.org/html/rfc3986#section-4.1 - * @return string - */ - public function __toString() - { - $scheme = $this->getScheme(); - $authority = $this->getAuthority(); - $path = $this->getPath(); - $query = $this->getQuery(); - $fragment = $this->getFragment(); - - $path = '/' . ltrim($path, '/'); - - return ($scheme !== '' ? $scheme . ':' : '') - . ($authority !== '' ? '//' . $authority : '') - . $path - . ($query !== '' ? '?' . $query : '') - . ($fragment !== '' ? '#' . $fragment : ''); - } - - /** - * Return the fully qualified base URL. - * - * Note that this method never includes a trailing / - * - * This method is not part of PSR-7. - * - * @return string - */ - public function getBaseUrl() - { - $scheme = $this->getScheme(); - $authority = $this->getAuthority(); - - return ($scheme !== '' ? $scheme . ':' : '') - . ($authority !== '' ? '//' . $authority : ''); - } -} diff --git a/tests/BodyTest.php b/tests/BodyTest.php deleted file mode 100644 index 3c9cba4..0000000 --- a/tests/BodyTest.php +++ /dev/null @@ -1,416 +0,0 @@ -stream) === true) { - fclose($this->stream); - } - } - - /** - * This method creates a new resource, and it seeds - * the resource with lorem ipsum text. The returned - * resource is readable, writable, and seekable. - * - * @param string $mode - * - * @return resource - */ - public function resourceFactory($mode = 'r+') - { - $stream = fopen('php://temp', $mode); - fwrite($stream, $this->text); - rewind($stream); - - return $stream; - } - - public function testConstructorAttachesStream() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - - $this->assertSame($this->stream, $bodyStream->getValue($body)); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testConstructorInvalidStream() - { - $this->stream = 'foo'; - $body = new Body($this->stream); - } - - public function testGetMetadata() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue(is_array($body->getMetadata())); - } - - public function testGetMetadataKey() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals('php://temp', $body->getMetadata('uri')); - } - - public function testGetMetadataKeyNotFound() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertNull($body->getMetadata('foo')); - } - - public function testDetach() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - - $bodyMetadata = new ReflectionProperty($body, 'meta'); - $bodyMetadata->setAccessible(true); - - $bodyReadable = new ReflectionProperty($body, 'readable'); - $bodyReadable->setAccessible(true); - - $bodyWritable = new ReflectionProperty($body, 'writable'); - $bodyWritable->setAccessible(true); - - $bodySeekable = new ReflectionProperty($body, 'seekable'); - $bodySeekable->setAccessible(true); - - $result = $body->detach(); - - $this->assertSame($this->stream, $result); - $this->assertNull($bodyStream->getValue($body)); - $this->assertNull($bodyMetadata->getValue($body)); - $this->assertNull($bodyReadable->getValue($body)); - $this->assertNull($bodyWritable->getValue($body)); - $this->assertNull($bodySeekable->getValue($body)); - } - - public function testToStringAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals($this->text, (string)$body); - } - - public function testToStringAttachedRewindsFirst() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals($this->text, (string)$body); - $this->assertEquals($this->text, (string)$body); - $this->assertEquals($this->text, (string)$body); - } - - public function testToStringDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->assertEquals('', (string)$body); - } - - public function testClose() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->close(); - - $this->assertAttributeEquals(null, 'stream', $body); - //$this->assertFalse($body->isAttached()); #1269 - } - - public function testGetSizeAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals(mb_strlen($this->text), $body->getSize()); - } - - public function testGetSizeDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->assertNull($body->getSize()); - } - - public function testTellAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - - $this->assertEquals(10, $body->tell()); - } - - /** - * @expectedException \RuntimeException - */ - public function testTellDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $body->tell(); - } - - public function testEofAttachedFalse() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - - $this->assertFalse($body->eof()); - } - - public function testEofAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - while (feof($this->stream) === false) { - fread($this->stream, 1024); - } - - $this->assertTrue($body->eof()); - } - - public function testEofDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->assertTrue($body->eof()); - } - - public function isReadableAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue($body->isReadable()); - } - - public function isReadableAttachedFalse() - { - $stream = fopen('php://temp', 'w'); - $body = new Body($this->stream); - - $this->assertFalse($body->isReadable()); - fclose($stream); - } - - public function testIsReadableDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->assertFalse($body->isReadable()); - } - - public function isWritableAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue($body->isWritable()); - } - - public function isWritableAttachedFalse() - { - $stream = fopen('php://temp', 'r'); - $body = new Body($this->stream); - - $this->assertFalse($body->isWritable()); - fclose($stream); - } - - public function testIsWritableDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->assertFalse($body->isWritable()); - } - - public function isSeekableAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue($body->isSeekable()); - } - - // TODO: Is seekable is false when attached... how? - - public function testIsSeekableDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->assertFalse($body->isSeekable()); - } - - public function testSeekAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->seek(10); - - $this->assertEquals(10, ftell($this->stream)); - } - - /** - * @expectedException \RuntimeException - */ - public function testSeekDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $body->seek(10); - } - - public function testRewindAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - $body->rewind(); - - $this->assertEquals(0, ftell($this->stream)); - } - - /** - * @expectedException \RuntimeException - */ - public function testRewindDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $body->rewind(); - } - - public function testReadAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals(substr($this->text, 0, 10), $body->read(10)); - } - - /** - * @expectedException \RuntimeException - */ - public function testReadDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $body->read(10); - } - - public function testWriteAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - while (feof($this->stream) === false) { - fread($this->stream, 1024); - } - $body->write('foo'); - - $this->assertEquals($this->text . 'foo', (string)$body); - } - - /** - * @expectedException \RuntimeException - */ - public function testWriteDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $body->write('foo'); - } - - public function testGetContentsAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - - $this->assertEquals(substr($this->text, 10), $body->getContents()); - } - - /** - * @expectedException \RuntimeException - */ - public function testGetContentsDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $body->getContents(); - } -} diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php deleted file mode 100644 index 9a96013..0000000 --- a/tests/CollectionTest.php +++ /dev/null @@ -1,156 +0,0 @@ -bag = new Collection(); - $this->property = new ReflectionProperty($this->bag, 'data'); - $this->property->setAccessible(true); - } - - public function testInitializeWithData() - { - $bag = new Collection(['foo' => 'bar']); - $bagProperty = new ReflectionProperty($bag, 'data'); - $bagProperty->setAccessible(true); - - $this->assertEquals(['foo' => 'bar'], $bagProperty->getValue($bag)); - } - - public function testSet() - { - $this->bag->set('foo', 'bar'); - $this->assertArrayHasKey('foo', $this->property->getValue($this->bag)); - $bag = $this->property->getValue($this->bag); - $this->assertEquals('bar', $bag['foo']); - } - - public function testOffsetSet() - { - $this->bag['foo'] = 'bar'; - $this->assertArrayHasKey('foo', $this->property->getValue($this->bag)); - $bag = $this->property->getValue($this->bag); - $this->assertEquals('bar', $bag['foo']); - } - - public function testGet() - { - $this->property->setValue($this->bag, ['foo' => 'bar']); - $this->assertEquals('bar', $this->bag->get('foo')); - } - - public function testGetWithDefault() - { - $this->property->setValue($this->bag, ['foo' => 'bar']); - $this->assertEquals('default', $this->bag->get('abc', 'default')); - } - - public function testReplace() - { - $this->bag->replace([ - 'abc' => '123', - 'foo' => 'bar', - ]); - $this->assertArrayHasKey('abc', $this->property->getValue($this->bag)); - $this->assertArrayHasKey('foo', $this->property->getValue($this->bag)); - $bag = $this->property->getValue($this->bag); - $this->assertEquals('123', $bag['abc']); - $this->assertEquals('bar', $bag['foo']); - } - - public function testAll() - { - $data = [ - 'abc' => '123', - 'foo' => 'bar', - ]; - $this->property->setValue($this->bag, $data); - $this->assertEquals($data, $this->bag->all()); - } - - public function testKeys() - { - $data = [ - 'abc' => '123', - 'foo' => 'bar', - ]; - $this->property->setValue($this->bag, $data); - $this->assertEquals(['abc', 'foo'], $this->bag->keys()); - } - - public function testHas() - { - $this->property->setValue($this->bag, ['foo' => 'bar']); - $this->assertTrue($this->bag->has('foo')); - $this->assertFalse($this->bag->has('abc')); - } - - public function testOffsetExists() - { - $this->property->setValue($this->bag, ['foo' => 'bar']); - $this->assertTrue(isset($this->bag['foo'])); - } - - public function testRemove() - { - $data = [ - 'abc' => '123', - 'foo' => 'bar', - ]; - $this->property->setValue($this->bag, $data); - $this->bag->remove('foo'); - $this->assertEquals(['abc' => '123'], $this->property->getValue($this->bag)); - } - - public function testOffsetUnset() - { - $data = [ - 'abc' => '123', - 'foo' => 'bar', - ]; - $this->property->setValue($this->bag, $data); - - unset($this->bag['foo']); - $this->assertEquals(['abc' => '123'], $this->property->getValue($this->bag)); - } - - public function testClear() - { - $data = [ - 'abc' => '123', - 'foo' => 'bar', - ]; - $this->property->setValue($this->bag, $data); - $this->bag->clear(); - $this->assertEquals([], $this->property->getValue($this->bag)); - } - - public function testCount() - { - $this->property->setValue($this->bag, ['foo' => 'bar', 'abc' => '123']); - $this->assertEquals(2, $this->bag->count()); - } -} diff --git a/tests/CookiesTest.php b/tests/CookiesTest.php deleted file mode 100644 index ea40917..0000000 --- a/tests/CookiesTest.php +++ /dev/null @@ -1,235 +0,0 @@ - 'Works', - ]); - $prop = new ReflectionProperty($cookies, 'requestCookies'); - $prop->setAccessible(true); - $this->assertNotEmpty($prop->getValue($cookies)['test']); - $this->assertEquals('Works', $prop->getValue($cookies)['test']); - } - - public function testSetDefaults() - { - $defaults = [ - 'value' => 'toast', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => true, - 'httponly' => true - ]; - - $cookies = new Cookies; - - $prop = new ReflectionProperty($cookies, 'defaults'); - $prop->setAccessible(true); - - $origDefaults = $prop->getValue($cookies); - - $cookies->setDefaults($defaults); - - $this->assertEquals($defaults, $prop->getValue($cookies)); - $this->assertNotEquals($origDefaults, $prop->getValue($cookies)); - } - - public function testSetCookieValues() - { - $cookies = new Cookies; - $cookies->set('foo', 'bar'); - - $prop = new ReflectionProperty($cookies, 'responseCookies'); - $prop->setAccessible(true); - - //we expect all of these values with null/false defaults - $expectedValue = [ - 'foo' => [ - 'value' => 'bar', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => false, - 'httponly' => false - ] - ]; - - $this->assertEquals($expectedValue, $prop->getValue($cookies)); - } - - public function testSetCookieValuesContainDefaults() - { - $cookies = new Cookies; - $defaults = [ - 'value' => 'toast', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => true, - 'httponly' => true - ]; - - $cookies->setDefaults($defaults); - $cookies->set('foo', 'bar'); - - $prop = new ReflectionProperty($cookies, 'responseCookies'); - $prop->setAccessible(true); - - //we expect to have secure and httponly from defaults - $expectedValue = [ - 'foo' => [ - 'value' => 'bar', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => true, - 'httponly' => true - ] - ]; - - $this->assertEquals($expectedValue, $prop->getValue($cookies)); - } - - public function testSetCookieValuesCanOverrideDefaults() - { - $cookies = new Cookies; - $defaults = [ - 'value' => 'toast', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => true, - 'httponly' => true - ]; - - $cookies->setDefaults($defaults); - - //default has secure true, lets override it to false - $cookies->set('foo', ['value' => 'bar', 'secure' => false]); - - $prop = new ReflectionProperty($cookies, 'responseCookies'); - $prop->setAccessible(true); - - $expectedValue = [ - 'foo' => [ - 'value' => 'bar', - 'domain' => null, - 'hostonly' => null, - 'path' => null, - 'expires' => null, - 'secure' => false, - 'httponly' => true - ] - ]; - - $this->assertEquals($expectedValue, $prop->getValue($cookies)); - } - - public function testGet() - { - $cookies = new Cookies(['foo' => 'bar']); - $this->assertEquals('bar', $cookies->get('foo')); - $this->assertNull($cookies->get('missing')); - $this->assertEquals('defaultValue', $cookies->get('missing', 'defaultValue')); - } - - public function testParseHeader() - { - $cookies = Cookies::parseHeader('foo=bar; name=Josh'); - $this->assertEquals('bar', $cookies['foo']); - $this->assertEquals('Josh', $cookies['name']); - } - - public function testParseHeaderWithJsonArray() - { - $cookies = Cookies::parseHeader('foo=bar; testarray=["someVar1","someVar2","someVar3"]'); - $this->assertEquals('bar', $cookies['foo']); - $this->assertContains('someVar3', json_decode($cookies['testarray'])); - } - - public function testToHeaders() - { - $cookies = new Cookies; - $cookies->set('test', 'Works'); - $cookies->set('test_array', ['value' => 'bar', 'domain' => 'example.com']); - $this->assertEquals('test=Works', $cookies->toHeaders()[0]); - $this->assertEquals('test_array=bar; domain=example.com', $cookies->toHeaders()[1]); - } - - public function testToHeader() - { - $cookies = new Cookies(); - $class = new ReflectionClass($cookies); - $method = $class->getMethod('toHeader'); - $method->setAccessible(true); - $properties = [ - 'name' => 'test', - 'properties' => [ - 'value' => 'Works' - ] - ]; - $time = time(); - $formattedDate = gmdate('D, d-M-Y H:i:s e', $time); - $propertiesComplex = [ - 'name' => 'test_complex', - 'properties' => [ - 'value' => 'Works', - 'domain' => 'example.com', - 'expires' => $time, - 'path' => '/', - 'secure' => true, - 'hostonly' => true, - 'httponly' => true - ] - ]; - $stringDate = '2016-01-01 12:00:00'; - $formattedStringDate = gmdate('D, d-M-Y H:i:s e', strtotime($stringDate)); - $propertiesStringDate = [ - 'name' => 'test_date', - 'properties' => [ - 'value' => 'Works', - 'expires' => $stringDate, - ] - ]; - $cookie = $method->invokeArgs($cookies, $properties); - $cookieComplex = $method->invokeArgs($cookies, $propertiesComplex); - $cookieStringDate = $method->invokeArgs($cookies, $propertiesStringDate); - $this->assertEquals('test=Works', $cookie); - $this->assertEquals( - 'test_complex=Works; domain=example.com; path=/; expires=' - . $formattedDate . '; secure; HostOnly; HttpOnly', - $cookieComplex - ); - $this->assertEquals('test_date=Works; expires=' . $formattedStringDate, $cookieStringDate); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testParseHeaderException() - { - Cookies::parseHeader(new \StdClass); - } -} diff --git a/tests/EnvironmentTest.php b/tests/EnvironmentTest.php deleted file mode 100644 index 1d27886..0000000 --- a/tests/EnvironmentTest.php +++ /dev/null @@ -1,58 +0,0 @@ - '/foo/bar/index.php', - 'REQUEST_URI' => '/foo/bar?abc=123', - ]); - - $this->assertEquals('/foo/bar/index.php', $env['SCRIPT_NAME']); - $this->assertEquals('/foo/bar?abc=123', $env['REQUEST_URI']); - $this->assertEquals('localhost', $env['HTTP_HOST']); - } - - /** - * Test environment from mock data with HTTPS - */ - public function testMockHttps() - { - $env = Environment::mock([ - 'HTTPS' => 'on' - ]); - - $this->assertInternalType('array', $env); - $this->assertEquals('on', $env['HTTPS']); - $this->assertEquals(443, $env['SERVER_PORT']); - } - - /** - * Test environment from mock data with REQUEST_SCHEME - */ - public function testMockRequestScheme() - { - $env = Environment::mock([ - 'REQUEST_SCHEME' => 'https' - ]); - - $this->assertInternalType('array', $env); - $this->assertEquals('https', $env['REQUEST_SCHEME']); - $this->assertEquals(443, $env['SERVER_PORT']); - } -} diff --git a/tests/HeadersTest.php b/tests/HeadersTest.php deleted file mode 100644 index c21771d..0000000 --- a/tests/HeadersTest.php +++ /dev/null @@ -1,251 +0,0 @@ - 'application/json', - ]); - $h = Headers::createFromGlobals($e); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['accept'])); - $this->assertEquals('application/json', $prop->getValue($h)['accept']['value'][0]); - $this->assertEquals('Accept', $prop->getValue($h)['accept']['originalKey']); - } - - public function testCreateFromGlobalsWithSpecialHeaders() - { - $e = Environment::mock([ - 'CONTENT_TYPE' => 'application/json', - ]); - $h = Headers::createFromGlobals($e); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['content-type'])); - $this->assertEquals('application/json', $prop->getValue($h)['content-type']['value'][0]); - $this->assertEquals('Content-Type', $prop->getValue($h)['content-type']['originalKey']); - } - - public function testCreateFromGlobalsIgnoresHeaders() - { - $e = Environment::mock([ - 'CONTENT_TYPE' => 'text/csv', - 'HTTP_CONTENT_LENGTH' => 1230, // <-- Ignored - ]); - $h = Headers::createFromGlobals($e); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertNotContains('content-length', $prop->getValue($h)); - $this->assertEquals('Content-Type', $prop->getValue($h)['content-type']['originalKey']); - } - - public function testConstructor() - { - $h = new Headers([ - 'Content-Length' => 100, - ]); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['content-length'])); - $this->assertEquals(100, $prop->getValue($h)['content-length']['value'][0]); - } - - public function testSetSingleValue() - { - $h = new Headers(); - $h->set('Content-Length', 100); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['content-length'])); - $this->assertEquals(100, $prop->getValue($h)['content-length']['value'][0]); - } - - public function testSetArrayValue() - { - $h = new Headers(); - $h->set('Allow', ['GET', 'POST']); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['allow'])); - $this->assertEquals(['GET', 'POST'], $prop->getValue($h)['allow']['value']); - } - - public function testGet() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'Allow' - ] - ]); - - $this->assertEquals(['GET', 'POST'], $h->get('Allow')); - } - - public function testGetOriginalKey() - { - $h = new Headers(); - $h->set('http-test_key', 'testValue'); - $h->get('test-key'); - - $value = $h->get('test-key'); - - $this->assertEquals('testValue', reset($value)); - $this->assertEquals('http-test_key', $h->getOriginalKey('test-key')); - $this->assertNull($h->getOriginalKey('test-non-existing')); - } - - public function testGetNotExists() - { - $h = new Headers(); - - $this->assertNull($h->get('Foo')); - } - - public function testAddNewValue() - { - $h = new Headers(); - $h->add('Foo', 'Bar'); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['foo'])); - $this->assertEquals(['Bar'], $prop->getValue($h)['foo']['value']); - } - - public function testAddAnotherValue() - { - $h = new Headers(); - $h->add('Foo', 'Bar'); - $h->add('Foo', 'Xyz'); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['foo'])); - $this->assertEquals(['Bar', 'Xyz'], $prop->getValue($h)['foo']['value']); - } - - public function testAddArrayValue() - { - $h = new Headers(); - $h->add('Foo', 'Bar'); - $h->add('Foo', ['Xyz', '123']); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['foo'])); - $this->assertEquals(['Bar', 'Xyz', '123'], $prop->getValue($h)['foo']['value']); - } - - public function testHas() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'Allow' - ] - ]); - $this->assertTrue($h->has('allow')); - $this->assertFalse($h->has('foo')); - } - - public function testRemove() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'Allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'Allow' - ] - ]); - $h->remove('Allow'); - - $this->assertNotContains('Allow', $prop->getValue($h)); - } - - public function testOriginalKeys() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'Allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'ALLOW' - ] - ]); - $all = $h->all(); - - $this->assertArrayHasKey('ALLOW', $all); - } - - public function testNormalizeKey() - { - $h = new Headers(); - $this->assertEquals('foo-bar', $h->normalizeKey('HTTP_FOO_BAR')); - $this->assertEquals('foo-bar', $h->normalizeKey('HTTP-FOO-BAR')); - $this->assertEquals('foo-bar', $h->normalizeKey('Http-Foo-Bar')); - $this->assertEquals('foo-bar', $h->normalizeKey('Http_Foo_Bar')); - $this->assertEquals('foo-bar', $h->normalizeKey('http_foo_bar')); - $this->assertEquals('foo-bar', $h->normalizeKey('http-foo-bar')); - } - - public function testDetermineAuthorization() - { - $e = Environment::mock([]); - $en = Headers::determineAuthorization($e); - $h = Headers::createFromGlobals($e); - - $this->assertEquals('electrolytes', $en['HTTP_AUTHORIZATION']); - $this->assertEquals(['electrolytes'], $h['Authorization']); - } - - public function testDetermineAuthorizationHonoursHttpAuthorizationKey() - { - $e = Environment::mock(['HTTP_AUTHORIZATION' => 'foo']); - $en = Headers::determineAuthorization($e); - $h = Headers::createFromGlobals($e); - - $this->assertEquals('foo', $en['HTTP_AUTHORIZATION']); - $this->assertEquals(['foo'], $h['Authorization']); - } - - public function testDetermineAuthorizationWhenEmpty() - { - $e = Environment::mock(['HTTP_AUTHORIZATION' => '']); - $en = Headers::determineAuthorization($e); - $h = Headers::createFromGlobals($e); - - $this->assertEquals('electrolytes', $en['HTTP_AUTHORIZATION']); - $this->assertEquals(['electrolytes'], $h['Authorization']); - } -} diff --git a/tests/Integration/BaseTestFactories.php b/tests/Integration/BaseTestFactories.php deleted file mode 100644 index da6cb85..0000000 --- a/tests/Integration/BaseTestFactories.php +++ /dev/null @@ -1,51 +0,0 @@ -buildUri('/'), new Headers(), [], $_SERVER, $this->buildStream('')); - } -} diff --git a/tests/Integration/StreamTest.php b/tests/Integration/StreamTest.php deleted file mode 100644 index 0de6f4c..0000000 --- a/tests/Integration/StreamTest.php +++ /dev/null @@ -1,38 +0,0 @@ -protocolVersion = '1.0'; - - $this->assertEquals('1.0', $message->getProtocolVersion()); - } - - /** - * @covers Slim\Http\Message::withProtocolVersion - */ - public function testWithProtocolVersion() - { - $message = new MessageStub(); - $clone = $message->withProtocolVersion('1.0'); - - $this->assertEquals('1.0', $clone->protocolVersion); - } - - /** - * @covers Slim\Http\Message::withProtocolVersion - * @expectedException \InvalidArgumentException - */ - public function testWithProtocolVersionInvalidThrowsException() - { - $message = new MessageStub(); - $message->withProtocolVersion('3.0'); - } - - /******************************************************************************* - * Headers - ******************************************************************************/ - - /** - * @covers Slim\Http\Message::getHeaders - */ - public function testGetHeaders() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Foo', 'two'); - $headers->add('X-Foo', 'three'); - - $message = new MessageStub(); - $message->headers = $headers; - - $shouldBe = [ - 'X-Foo' => [ - 'one', - 'two', - 'three', - ], - ]; - - $this->assertEquals($shouldBe, $message->getHeaders()); - } - - /** - * @covers Slim\Http\Message::hasHeader - */ - public function testHasHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - - $message = new MessageStub(); - $message->headers = $headers; - - $this->assertTrue($message->hasHeader('X-Foo')); - $this->assertFalse($message->hasHeader('X-Bar')); - } - - /** - * @covers Slim\Http\Message::getHeaderLine - */ - public function testGetHeaderLine() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Foo', 'two'); - $headers->add('X-Foo', 'three'); - - $message = new MessageStub(); - $message->headers = $headers; - - $this->assertEquals('one,two,three', $message->getHeaderLine('X-Foo')); - $this->assertEquals('', $message->getHeaderLine('X-Bar')); - } - - /** - * @covers Slim\Http\Message::getHeader - */ - public function testGetHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Foo', 'two'); - $headers->add('X-Foo', 'three'); - - $message = new MessageStub(); - $message->headers = $headers; - - $this->assertEquals(['one', 'two', 'three'], $message->getHeader('X-Foo')); - $this->assertEquals([], $message->getHeader('X-Bar')); - } - - /** - * @covers Slim\Http\Message::withHeader - */ - public function testWithHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $message = new MessageStub(); - $message->headers = $headers; - $clone = $message->withHeader('X-Foo', 'bar'); - - $this->assertEquals('bar', $clone->getHeaderLine('X-Foo')); - } - - /** - * @covers Slim\Http\Message::withAddedHeader - */ - public function testWithAddedHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $message = new MessageStub(); - $message->headers = $headers; - $clone = $message->withAddedHeader('X-Foo', 'two'); - - $this->assertEquals('one,two', $clone->getHeaderLine('X-Foo')); - } - - /** - * @covers Slim\Http\Message::withoutHeader - */ - public function testWithoutHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Bar', 'two'); - $response = new MessageStub(); - $response->headers = $headers; - $clone = $response->withoutHeader('X-Foo'); - $shouldBe = [ - 'X-Bar' => ['two'], - ]; - - $this->assertEquals($shouldBe, $clone->getHeaders()); - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * @covers Slim\Http\Message::getBody - */ - public function testGetBody() - { - $body = $this->getBody(); - $message = new MessageStub(); - $message->body = $body; - - $this->assertSame($body, $message->getBody()); - } - - /** - * @covers Slim\Http\Message::withBody - */ - public function testWithBody() - { - $body = $this->getBody(); - $body2 = $this->getBody(); - $message = new MessageStub(); - $message->body = $body; - $clone = $message->withBody($body2); - - $this->assertSame($body, $message->body); - $this->assertSame($body2, $clone->body); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|\Slim\Http\Body - */ - protected function getBody() - { - return $this->getMockBuilder('Slim\Http\Body')->disableOriginalConstructor()->getMock(); - } -} diff --git a/tests/Mocks/MessageStub.php b/tests/Mocks/MessageStub.php deleted file mode 100644 index 61a8087..0000000 --- a/tests/Mocks/MessageStub.php +++ /dev/null @@ -1,38 +0,0 @@ -body = new RequestBody(); - $this->body->write($this->text); - $this->body->rewind(); - } - - protected function tearDown() - { - if (is_resource($this->stream) === true) { - fclose($this->stream); - } - $this->body = null; - } - - /** - * This method creates a new resource, and it seeds - * the resource with lorem ipsum text. The returned - * resource is readable, writable, and seekable. - * - * @param string $mode - * - * @return resource - */ - public function resourceFactory($mode = 'r+') - { - $stream = fopen('php://temp', $mode); - fwrite($stream, $this->text); - rewind($stream); - - return $stream; - } - - public function testConstructorAttachesStream() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - - $this->assertInternalType('resource', $bodyStream->getValue($this->body)); - } - - public function testConstructorSetsMetadata() - { - $bodyMetadata = new ReflectionProperty($this->body, 'meta'); - $bodyMetadata->setAccessible(true); - - $this->assertTrue(is_array($bodyMetadata->getValue($this->body))); - } - - public function testGetMetadata() - { - $this->assertTrue(is_array($this->body->getMetadata())); - } - - public function testGetMetadataKey() - { - $this->assertEquals('php://temp', $this->body->getMetadata('uri')); - } - - public function testGetMetadataKeyNotFound() - { - $this->assertNull($this->body->getMetadata('foo')); - } - - public function testDetach() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - - $bodyMetadata = new ReflectionProperty($this->body, 'meta'); - $bodyMetadata->setAccessible(true); - - $bodyReadable = new ReflectionProperty($this->body, 'readable'); - $bodyReadable->setAccessible(true); - - $bodyWritable = new ReflectionProperty($this->body, 'writable'); - $bodyWritable->setAccessible(true); - - $bodySeekable = new ReflectionProperty($this->body, 'seekable'); - $bodySeekable->setAccessible(true); - - $result = $this->body->detach(); - - $this->assertInternalType('resource', $result); - $this->assertNull($bodyStream->getValue($this->body)); - $this->assertNull($bodyMetadata->getValue($this->body)); - $this->assertNull($bodyReadable->getValue($this->body)); - $this->assertNull($bodyWritable->getValue($this->body)); - $this->assertNull($bodySeekable->getValue($this->body)); - } - - public function testToStringAttached() - { - $this->assertEquals($this->text, (string)$this->body); - } - - public function testToStringAttachedRewindsFirst() - { - $this->assertEquals($this->text, (string)$this->body); - $this->assertEquals($this->text, (string)$this->body); - $this->assertEquals($this->text, (string)$this->body); - } - - public function testToStringDetached() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->assertEquals('', (string)$this->body); - } - - /** - * @expectedException \RuntimeException - */ - public function testClose() - { - $this->body->close(); - - $this->assertAttributeEquals(null, 'stream', $this->body); - $this->assertFalse($this->body->isReadable()); - $this->assertFalse($this->body->isWritable()); - $this->assertEquals('', (string)$this->body); - - $this->body->tell(); - } - - public function testGetSizeAttached() - { - $this->assertEquals(mb_strlen($this->text), $this->body->getSize()); - } - - public function testGetSizeDetached() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->assertNull($this->body->getSize()); - } - - public function testTellAttached() - { - $this->body->seek(10); - - $this->assertEquals(10, $this->body->tell()); - } - - /** - * @expectedException \RuntimeException - */ - public function testTellDetachedThrowsRuntimeException() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->body->tell(); - } - - public function testEofAttachedFalse() - { - $this->body->seek(10); - - $this->assertFalse($this->body->eof()); - } - - public function testEofAttachedTrue() - { - while ($this->body->eof() === false) { - $this->body->read(1024); - } - - $this->assertTrue($this->body->eof()); - } - - public function testEofDetached() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->assertTrue($this->body->eof()); - } - - public function testIsReadableAttachedTrue() - { - $this->assertTrue($this->body->isReadable()); - } - - public function testIsReadableDetached() - { - $this->body->detach(); - - $this->assertFalse($this->body->isReadable()); - } - - public function testIsWritableAttachedTrue() - { - $this->assertTrue($this->body->isWritable()); - } - - public function testIsWritableDetached() - { - $this->body->detach(); - - $this->assertFalse($this->body->isWritable()); - } - - public function isSeekableAttachedTrue() - { - $this->assertTrue($this->body->isSeekable()); - } - - // TODO: Is seekable is false when attached... how? - - public function testIsSeekableDetached() - { - $this->body->detach(); - - $this->assertFalse($this->body->isSeekable()); - } - - public function testSeekAttached() - { - $this->body->seek(10); - - $this->assertEquals(10, $this->body->tell()); - } - - /** - * @expectedException \RuntimeException - */ - public function testSeekDetachedThrowsRuntimeException() - { - $this->body->detach(); - $this->body->seek(10); - } - - public function testRewindAttached() - { - $this->body->seek(10); - $this->body->rewind(); - - $this->assertEquals(0, $this->body->tell()); - } - - /** - * @expectedException \RuntimeException - */ - public function testRewindDetachedThrowsRuntimeException() - { - $this->body->detach(); - $this->body->rewind(); - } - - public function testReadAttached() - { - $this->assertEquals(substr($this->text, 0, 10), $this->body->read(10)); - } - - /** - * @expectedException \RuntimeException - */ - public function testReadDetachedThrowsRuntimeException() - { - $this->body->detach(); - $this->body->read(10); - } - - public function testWriteAttached() - { - while ($this->body->eof() === false) { - $this->body->read(1024); - } - $this->body->write('foo'); - - $this->assertEquals($this->text . 'foo', (string)$this->body); - } - - /** - * @expectedException \RuntimeException - */ - public function testWriteDetachedThrowsRuntimeException() - { - $this->body->detach(); - $this->body->write('foo'); - } - - public function testGetContentsAttached() - { - $this->body->seek(10); - - $this->assertEquals(substr($this->text, 10), $this->body->getContents()); - } - - /** - * @expectedException \RuntimeException - */ - public function testGetContentsDetachedThrowsRuntimeException() - { - $this->body->detach(); - $this->body->getContents(); - } -} diff --git a/tests/RequestTest.php b/tests/RequestTest.php deleted file mode 100644 index 75dcfcb..0000000 --- a/tests/RequestTest.php +++ /dev/null @@ -1,1094 +0,0 @@ - 'john', - 'id' => '123', - ]; - $serverParams = $env; - $body = new RequestBody(); - $uploadedFiles = UploadedFile::createFromGlobals($env); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - return $request; - } - - public function testDisableSetter() - { - $request = $this->requestFactory(); - $request->foo = 'bar'; - - $this->assertFalse(property_exists($request, 'foo')); - } - - public function testAddsHostHeaderFromUri() - { - $request = $this->requestFactory(); - $this->assertEquals('example.com', $request->getHeaderLine('Host')); - } - - /******************************************************************************* - * Method - ******************************************************************************/ - - public function testGetMethod() - { - $this->assertEquals('GET', $this->requestFactory()->getMethod()); - } - - public function testWithMethod() - { - $request = $this->requestFactory()->withMethod('PUT'); - - $this->assertAttributeEquals('PUT', 'method', $request); - } - - public function testWithMethodCaseSensitive() - { - $request = $this->requestFactory()->withMethod('pOsT'); - - $this->assertAttributeEquals('pOsT', 'method', $request); - } - - public function testWithAllAllowedCharactersMethod() - { - $request = $this->requestFactory()->withMethod("!#$%&'*+.^_`|~09AZ-"); - - $this->assertAttributeEquals("!#$%&'*+.^_`|~09AZ-", 'method', $request); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testWithMethodInvalid() - { - $this->requestFactory()->withMethod('B@R'); - } - - public function testWithMethodNull() - { - $request = $this->requestFactory()->withMethod(null); - - $this->assertAttributeEquals(null, 'method', $request); - } - - /** - * @covers Slim\Http\Request::createFromGlobals - */ - public function testCreateFromGlobals() - { - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'POST', - ]); - - $request = Request::createFromGlobals($env); - $this->assertEquals('POST', $request->getMethod()); - $this->assertEquals($env, $request->getServerParams()); - } - - /** - * @covers Slim\Http\Request::createFromGlobals - */ - public function testCreateFromGlobalsWithMultipart() - { - $_POST['foo'] = 'bar'; - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'POST', - 'HTTP_CONTENT_TYPE' => 'multipart/form-data; boundary=---foo' - ]); - - $request = Request::createFromGlobals($env); - unset($_POST); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testCreateRequestWithInvalidMethodString() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('B@R', $uri, $headers, $cookies, $serverParams, $body); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testCreateRequestWithInvalidMethodOther() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request(10, $uri, $headers, $cookies, $serverParams, $body); - } - - public function testIsGet() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'GET'); - - $this->assertTrue($request->isGet()); - } - - public function testIsPost() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'POST'); - - $this->assertTrue($request->isPost()); - } - - public function testIsPut() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'PUT'); - - $this->assertTrue($request->isPut()); - } - - public function testIsPatch() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'PATCH'); - - $this->assertTrue($request->isPatch()); - } - - public function testIsDelete() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'DELETE'); - - $this->assertTrue($request->isDelete()); - } - - public function testIsHead() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'HEAD'); - - $this->assertTrue($request->isHead()); - } - - public function testIsOptions() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'method'); - $prop->setAccessible(true); - $prop->setValue($request, 'OPTIONS'); - - $this->assertTrue($request->isOptions()); - } - - public function testIsXhr() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded', - 'X-Requested-With' => 'XMLHttpRequest', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertTrue($request->isXhr()); - } - - /******************************************************************************* - * URI - ******************************************************************************/ - - public function testGetRequestTarget() - { - $this->assertEquals('/foo/bar?abc=123', $this->requestFactory()->getRequestTarget()); - } - - public function testGetRequestTargetAlreadySet() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'requestTarget'); - $prop->setAccessible(true); - $prop->setValue($request, '/foo/bar?abc=123'); - - $this->assertEquals('/foo/bar?abc=123', $request->getRequestTarget()); - } - - public function testGetRequestTargetIfNoUri() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'uri'); - $prop->setAccessible(true); - $prop->setValue($request, null); - - $this->assertEquals('/', $request->getRequestTarget()); - } - - public function testWithRequestTarget() - { - $clone = $this->requestFactory()->withRequestTarget('/test?user=1'); - - $this->assertAttributeEquals('/test?user=1', 'requestTarget', $clone); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testWithRequestTargetThatHasSpaces() - { - $this->requestFactory()->withRequestTarget('/test/m ore/stuff?user=1'); - } - - public function testGetUri() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertSame($uri, $request->getUri()); - } - - public function testWithUri() - { - // Uris - $uri1 = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $uri2 = Uri::createFromString('https://example2.com:443/test?xyz=123'); - - // Request - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri1, $headers, $cookies, $serverParams, $body); - $clone = $request->withUri($uri2); - - $this->assertAttributeSame($uri2, 'uri', $clone); - } - - public function testWithUriPreservesHost() - { - // When `$preserveHost` is set to `true`, this method interacts with - // the Host header in the following ways: - - // - If the the Host header is missing or empty, and the new URI contains - // a host component, this method MUST update the Host header in the returned - // request. - $uri1 = Uri::createFromString(''); - $uri2 = Uri::createFromString('http://example2.com/test'); - - // Request - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri1, $headers, $cookies, $serverParams, $body); - - $clone = $request->withUri($uri2, true); - $this->assertSame('example2.com', $clone->getHeaderLine('Host')); - - // - If the Host header is missing or empty, and the new URI does not contain a - // host component, this method MUST NOT update the Host header in the returned - // request. - $uri3 = Uri::createFromString(''); - - $clone = $request->withUri($uri3, true); - $this->assertSame('', $clone->getHeaderLine('Host')); - - // - If a Host header is present and non-empty, this method MUST NOT update - // the Host header in the returned request. - $request = $request->withHeader('Host', 'example.com'); - $clone = $request->withUri($uri2, true); - $this->assertSame('example.com', $clone->getHeaderLine('Host')); - } - - public function testGetContentType() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals('application/json;charset=utf8', $request->getContentType()); - } - - public function testGetContentTypeEmpty() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getContentType()); - } - - public function testGetMediaType() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals('application/json', $request->getMediaType()); - } - - public function testGetMediaTypeEmpty() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getMediaType()); - } - - public function testGetMediaTypeParams() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8;foo=bar'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals(['charset' => 'utf8', 'foo' => 'bar'], $request->getMediaTypeParams()); - } - - public function testGetMediaTypeParamsEmpty() - { - $headers = new Headers([ - 'Content-Type' => ['application/json'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals([], $request->getMediaTypeParams()); - } - - public function testGetMediaTypeParamsWithoutHeader() - { - $request = $this->requestFactory(); - - $this->assertEquals([], $request->getMediaTypeParams()); - } - - public function testGetContentCharset() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals('utf8', $request->getContentCharset()); - } - - public function testGetContentCharsetEmpty() - { - $headers = new Headers([ - 'Content-Type' => ['application/json'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertNull($request->getContentCharset()); - } - - public function testGetContentCharsetWithoutHeader() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getContentCharset()); - } - - public function testGetContentLength() - { - $headers = new Headers([ - 'Content-Length' => '150', // <-- Note we define as a string - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals(150, $request->getContentLength()); - } - - public function testGetContentLengthWithoutHeader() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getContentLength()); - } - - /******************************************************************************* - * Cookies - ******************************************************************************/ - - public function testGetCookieParam() - { - $shouldBe = 'john'; - - $this->assertEquals($shouldBe, $this->requestFactory()->getCookieParam('user')); - } - - public function testGetCookieParamWithDefault() - { - $shouldBe = 'bar'; - - $this->assertEquals($shouldBe, $this->requestFactory()->getCookieParam('foo', 'bar')); - } - - public function testGetCookieParams() - { - $shouldBe = [ - 'user' => 'john', - 'id' => '123', - ]; - - $this->assertEquals($shouldBe, $this->requestFactory()->getCookieParams()); - } - - public function testWithCookieParams() - { - $request = $this->requestFactory(); - $clone = $request->withCookieParams(['type' => 'framework']); - - $this->assertEquals(['type' => 'framework'], $clone->getCookieParams()); - } - - /******************************************************************************* - * Query Params - ******************************************************************************/ - - public function testGetQueryParams() - { - $this->assertEquals(['abc' => '123'], $this->requestFactory()->getQueryParams()); - } - - public function testGetQueryParamsAlreadySet() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'queryParams'); - $prop->setAccessible(true); - $prop->setValue($request, ['foo' => 'bar']); - - $this->assertEquals(['foo' => 'bar'], $request->getQueryParams()); - } - - public function testWithQueryParams() - { - $request = $this->requestFactory(); - $clone = $request->withQueryParams(['foo' => 'bar']); - $cloneUri = $clone->getUri(); - - $this->assertEquals('abc=123', $cloneUri->getQuery()); // <-- Unchanged - $this->assertEquals(['foo' => 'bar'], $clone->getQueryParams()); // <-- Changed - } - - public function testWithQueryParamsEmptyArray() - { - $request = $this->requestFactory(); - $clone = $request->withQueryParams([]); - $cloneUri = $clone->getUri(); - - $this->assertEquals('abc=123', $cloneUri->getQuery()); // <-- Unchanged - $this->assertEquals([], $clone->getQueryParams()); // <-- Changed - } - - public function testGetQueryParamsWithoutUri() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'uri'); - $prop->setAccessible(true); - $prop->setValue($request, null); - - $this->assertEquals([], $request->getQueryParams()); - } - - /******************************************************************************* - * Uploaded files - ******************************************************************************/ - - /** - * @covers Slim\Http\Request::withUploadedFiles - * @covers Slim\Http\Request::getUploadedFiles - */ - public function testWithUploadedFiles() - { - $files = [new UploadedFile('foo.txt'), new UploadedFile('bar.txt')]; - - $request = $this->requestFactory(); - $clone = $request->withUploadedFiles($files); - - $this->assertEquals([], $request->getUploadedFiles()); - $this->assertEquals($files, $clone->getUploadedFiles()); - } - - /******************************************************************************* - * Server Params - ******************************************************************************/ - - public function testGetServerParams() - { - $mockEnv = Environment::mock(["HTTP_AUTHORIZATION" => "test"]); - $request = $this->requestFactory(["HTTP_AUTHORIZATION" => "test"]); - - $serverParams = $request->getServerParams(); - foreach ($serverParams as $key => $value) { - if ($key == 'REQUEST_TIME' || $key == 'REQUEST_TIME_FLOAT') { - $this->assertGreaterThanOrEqual( - $mockEnv[$key], - $value, - sprintf("%s value of %s was less than expected value of %s", $key, $value, $mockEnv[$key]) - ); - } else { - $this->assertEquals( - $mockEnv[$key], - $value, - sprintf("%s value of %s did not equal expected value of %s", $key, $value, $mockEnv[$key]) - ); - } - } - } - - public function testGetServerParam() - { - $shouldBe = 'HTTP/1.1'; - $request = $this->requestFactory(['SERVER_PROTOCOL' => 'HTTP/1.1']); - - $this->assertEquals($shouldBe, $this->requestFactory()->getServerParam('SERVER_PROTOCOL')); - } - - public function testGetServerParamWithDefault() - { - $shouldBe = 'bar'; - - $this->assertEquals($shouldBe, $this->requestFactory()->getServerParam('HTTP_NOT_EXIST', 'bar')); - } - - /******************************************************************************* - * File Params - ******************************************************************************/ - - /******************************************************************************* - * Attributes - ******************************************************************************/ - - public function testGetAttributes() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new Collection(['foo' => 'bar'])); - - $this->assertEquals(['foo' => 'bar'], $request->getAttributes()); - } - - public function testGetAttribute() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new Collection(['foo' => 'bar'])); - - $this->assertEquals('bar', $request->getAttribute('foo')); - $this->assertNull($request->getAttribute('bar')); - $this->assertEquals(2, $request->getAttribute('bar', 2)); - } - - public function testWithAttribute() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new Collection(['foo' => 'bar'])); - $clone = $request->withAttribute('test', '123'); - - $this->assertEquals('123', $clone->getAttribute('test')); - } - - public function testWithAttributes() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new Collection(['foo' => 'bar'])); - $clone = $request->withAttributes(['test' => '123']); - - $this->assertNull($clone->getAttribute('foo')); - $this->assertEquals('123', $clone->getAttribute('test')); - } - - public function testWithoutAttribute() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new Collection(['foo' => 'bar'])); - $clone = $request->withoutAttribute('foo'); - - $this->assertNull($clone->getAttribute('foo')); - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - public function testGetParsedBodyForm() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/x-www-form-urlencoded;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('foo=bar'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyJson() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/json;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{"foo":"bar"}'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyInvalidJson() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/json;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{foo}bar'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertNull($request->getParsedBody()); - } - - public function testGetParsedBodySemiValidJson() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/json;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('"foo bar"'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertNull($request->getParsedBody()); - } - - public function testGetParsedBodyWithJsonStructuredSuffix() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/vnd.api+json;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{"foo":"bar"}'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyXml() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/xml;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('Josh', $request->getParsedBody()->name); - } - - public function testGetParsedBodyWithXmlStructuredSuffix() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/hal+xml;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('Josh', $request->getParsedBody()->name); - } - - public function testGetParsedBodyXmlWithTextXMLMediaType() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'text/xml'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('Josh', $request->getParsedBody()->name); - } - - /** - * Will fail if a simple_xml warning is created - */ - public function testInvalidXmlIsQuietForTextXml() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'text/xml'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(null, $request->getParsedBody()); - } - - /** - * Will fail if a simple_xml warning is created - */ - public function testInvalidXmlIsQuietForApplicationXml() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/xml'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(null, $request->getParsedBody()); - } - - - public function testGetParsedBodyWhenAlreadyParsed() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'bodyParsed'); - $prop->setAccessible(true); - $prop->setValue($request, ['foo' => 'bar']); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyWhenBodyDoesNotExist() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'body'); - $prop->setAccessible(true); - $prop->setValue($request, null); - - $this->assertNull($request->getParsedBody()); - } - - public function testGetParsedBodyAfterCallReparseBody() - { - $uri = Uri::createFromString('https://example.com:443/?one=1'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded;charset=utf8', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('foo=bar'); - $body->rewind(); - $request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - - $newBody = new RequestBody(); - $newBody->write('abc=123'); - $newBody->rewind(); - $request = $request->withBody($newBody); - $request->reparseBody(); - - $this->assertEquals(['abc' => '123'], $request->getParsedBody()); - } - - /** - * @expectedException \RuntimeException - */ - public function testGetParsedBodyAsArray() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'Content-Type' => 'application/json;charset=utf8', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{"foo": "bar"}'); - $body->rewind(); - $request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - $request->registerMediaTypeParser('application/json', function ($input) { - return 10; // <-- Return invalid body value - }); - $request->getParsedBody(); // <-- Triggers exception - } - - public function testWithParsedBody() - { - $clone = $this->requestFactory()->withParsedBody(['xyz' => '123']); - - $this->assertEquals(['xyz' => '123'], $clone->getParsedBody()); - } - - public function testWithParsedBodyEmptyArray() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/x-www-form-urlencoded;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('foo=bar'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - - $clone = $request->withParsedBody([]); - - $this->assertEquals([], $clone->getParsedBody()); - } - - public function testWithParsedBodyNull() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/x-www-form-urlencoded;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('foo=bar'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - - $clone = $request->withParsedBody(null); - - $this->assertNull($clone->getParsedBody()); - } - - public function testGetParsedBodyReturnsNullWhenThereIsNoBodyData() - { - $request = $this->requestFactory(['REQUEST_METHOD' => 'POST']); - - $this->assertNull($request->getParsedBody()); - } - - public function testGetParsedBodyReturnsNullWhenThereIsNoMediaTypeParserRegistered() - { - $request = $this->requestFactory([ - 'REQUEST_METHOD' => 'POST', - 'CONTENT_TYPE' => 'text/csv', - ]); - $request->getBody()->write('foo,bar,baz'); - - $this->assertNull($request->getParsedBody()); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testWithParsedBodyInvalid() - { - $this->requestFactory()->withParsedBody(2); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testWithParsedBodyInvalidFalseValue() - { - $this->requestFactory()->withParsedBody(false); - } - - /******************************************************************************* - * Parameters - ******************************************************************************/ - - public function testGetParameterFromBody() - { - $body = new RequestBody(); - $body->write('foo=bar'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals('bar', $request->getParam('foo')); - } - - public function testGetParameterFromBodyWithBodyParemeterHelper() - { - $body = new RequestBody(); - $body->write('foo=bar'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals('bar', $request->getParsedBodyParam('foo')); - } - - public function testGetParameterFromQuery() - { - $request = $this->requestFactory()->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals('123', $request->getParam('abc')); - } - - public function testGetParameterFromQueryWithQueryParemeterHelper() - { - $request = $this->requestFactory()->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals('123', $request->getQueryParam('abc')); - } - - public function testGetParameterFromBodyOverQuery() - { - $body = new RequestBody(); - $body->write('abc=xyz'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - $this->assertEquals('xyz', $request->getParam('abc')); - } - - public function testGetParameterWithDefaultFromBodyOverQuery() - { - $body = new RequestBody(); - $body->write('abc=xyz'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - $this->assertEquals('xyz', $request->getParam('abc')); - $this->assertEquals('bar', $request->getParam('foo', 'bar')); - } - - public function testGetParameters() - { - $body = new RequestBody(); - $body->write('foo=bar'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals(['abc' => '123', 'foo' => 'bar'], $request->getParams()); - } - - public function testGetParametersWithBodyPriority() - { - $body = new RequestBody(); - $body->write('foo=bar&abc=xyz'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals(['abc' => 'xyz', 'foo' => 'bar'], $request->getParams()); - } - - /******************************************************************************* - * Protocol - ******************************************************************************/ - - public function testGetProtocolVersion() - { - $env = Environment::mock(['SERVER_PROTOCOL' => 'HTTP/1.0']); - $request = Request::createFromGlobals($env); - - $this->assertEquals('1.0', $request->getProtocolVersion()); - } -} diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php deleted file mode 100755 index ce3b988..0000000 --- a/tests/ResponseTest.php +++ /dev/null @@ -1,341 +0,0 @@ -assertAttributeEquals(200, 'status', $response); - $this->assertAttributeInstanceOf('\Slim\Http\Headers', 'headers', $response); - $this->assertAttributeInstanceOf('\Psr\Http\Message\StreamInterface', 'body', $response); - } - - public function testConstructorWithCustomArgs() - { - $headers = new Headers(); - $body = new Body(fopen('php://temp', 'r+')); - $response = new Response(404, $headers, $body); - - $this->assertAttributeEquals(404, 'status', $response); - $this->assertAttributeSame($headers, 'headers', $response); - $this->assertAttributeSame($body, 'body', $response); - } - - public function testDeepCopyClone() - { - $headers = new Headers(); - $body = new Body(fopen('php://temp', 'r+')); - $response = new Response(404, $headers, $body); - $clone = clone $response; - - $this->assertAttributeEquals('1.1', 'protocolVersion', $clone); - $this->assertAttributeEquals(404, 'status', $clone); - $this->assertAttributeNotSame($headers, 'headers', $clone); - $this->assertAttributeSame($body, 'body', $clone); - } - - public function testDisableSetter() - { - $response = new Response(); - $response->foo = 'bar'; - - $this->assertFalse(property_exists($response, 'foo')); - } - - /******************************************************************************* - * Status - ******************************************************************************/ - - public function testGetStatusCode() - { - $response = new Response(); - $responseStatus = new ReflectionProperty($response, 'status'); - $responseStatus->setAccessible(true); - $responseStatus->setValue($response, '404'); - - $this->assertEquals(404, $response->getStatusCode()); - } - - public function testWithStatus() - { - $response = new Response(); - $clone = $response->withStatus(302); - - $this->assertAttributeEquals(302, 'status', $clone); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testWithStatusInvalidStatusCodeThrowsException() - { - $response = new Response(); - $response->withStatus(800); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage ReasonPhrase must be a string - */ - public function testWithStatusInvalidReasonPhraseThrowsException() - { - $response = new Response(); - $response->withStatus(200, null); - } - - public function testWithStatusEmptyReasonPhrase() - { - $responseWithNoMessage = new Response(310); - - $this->assertEquals('', $responseWithNoMessage->getReasonPhrase()); - } - - public function testGetReasonPhrase() - { - $response = new Response(404); - - $this->assertEquals('Not Found', $response->getReasonPhrase()); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage ReasonPhrase must be supplied for this code - */ - public function testMustSetReasonPhraseForUnrecognisedCode() - { - $response = new Response(); - $response = $response->withStatus(199); - } - - public function testSetReasonPhraseForUnrecognisedCode() - { - $response = new Response(); - $response = $response->withStatus(199, 'Random Message'); - - $this->assertEquals('Random Message', $response->getReasonPhrase()); - } - - public function testGetCustomReasonPhrase() - { - $response = new Response(); - $clone = $response->withStatus(200, 'Custom Phrase'); - - $this->assertEquals('Custom Phrase', $clone->getReasonPhrase()); - } - - /** - * @covers Slim\Http\Response::withRedirect - */ - public function testWithRedirect() - { - $response = new Response(200); - $clone = $response->withRedirect('/foo', 301); - $cloneWithDefaultStatus = $response->withRedirect('/foo'); - $cloneWithStatusMethod = $response->withStatus(301)->withRedirect('/foo'); - - $this->assertSame(200, $response->getStatusCode()); - $this->assertFalse($response->hasHeader('Location')); - - $this->assertSame(301, $clone->getStatusCode()); - $this->assertTrue($clone->hasHeader('Location')); - $this->assertEquals('/foo', $clone->getHeaderLine('Location')); - - $this->assertSame(302, $cloneWithDefaultStatus->getStatusCode()); - $this->assertTrue($cloneWithDefaultStatus->hasHeader('Location')); - $this->assertEquals('/foo', $cloneWithDefaultStatus->getHeaderLine('Location')); - - $this->assertSame(301, $cloneWithStatusMethod->getStatusCode()); - $this->assertTrue($cloneWithStatusMethod->hasHeader('Location')); - $this->assertEquals('/foo', $cloneWithStatusMethod->getHeaderLine('Location')); - } - - /******************************************************************************* - * Behaviors - ******************************************************************************/ - - public function testIsEmpty() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 204); - - $this->assertTrue($response->isEmpty()); - } - - public function testIsInformational() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 100); - - $this->assertTrue($response->isInformational()); - } - - public function testIsOk() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 200); - - $this->assertTrue($response->isOk()); - } - - public function testIsSuccessful() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 201); - - $this->assertTrue($response->isSuccessful()); - } - - public function testIsRedirect() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 302); - - $this->assertTrue($response->isRedirect()); - } - - public function testIsRedirection() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 308); - - $this->assertTrue($response->isRedirection()); - } - - public function testIsForbidden() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 403); - - $this->assertTrue($response->isForbidden()); - } - - public function testIsNotFound() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 404); - - $this->assertTrue($response->isNotFound()); - } - - public function testIsClientError() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 400); - - $this->assertTrue($response->isClientError()); - } - - public function testIsServerError() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 503); - - $this->assertTrue($response->isServerError()); - } - - public function testToString() - { - $output = 'HTTP/1.1 404 Not Found' . Response::EOL . - 'X-Foo: Bar' . Response::EOL . Response::EOL . - 'Where am I?'; - $this->expectOutputString($output); - $response = new Response(); - $response = $response->withStatus(404)->withHeader('X-Foo', 'Bar')->write('Where am I?'); - - echo $response; - } - - public function testWithJson() - { - $data = ['foo' => 'bar1&bar2']; - - $originalResponse = new Response(); - $response = $originalResponse->withJson($data, 201); - - $this->assertNotEquals($response->getStatusCode(), $originalResponse->getStatusCode()); - $this->assertEquals(201, $response->getStatusCode()); - $this->assertEquals('application/json;charset=utf-8', $response->getHeaderLine('Content-Type')); - - $body = $response->getBody(); - $body->rewind(); - $dataJson = $body->getContents(); //json_decode($body->getContents(), true); - - $originalBody = $originalResponse->getBody(); - $originalBody->rewind(); - $originalContents = $originalBody->getContents(); - - // test the original body hasn't be replaced - $this->assertNotEquals($dataJson, $originalContents); - - $this->assertEquals('{"foo":"bar1&bar2"}', $dataJson); - $this->assertEquals($data['foo'], json_decode($dataJson, true)['foo']); - - // Test encoding option - $response = $response->withJson($data, 200, JSON_HEX_AMP); - - $body = $response->getBody(); - $body->rewind(); - $dataJson = $body->getContents(); - - $this->assertEquals('{"foo":"bar1\u0026bar2"}', $dataJson); - $this->assertEquals($data['foo'], json_decode($dataJson, true)['foo']); - - $response = $response->withStatus(201)->withJson([]); - $this->assertEquals($response->getStatusCode(), 201); - } - - /** - * @expectedException \RuntimeException - */ - public function testWithInvalidJsonThrowsException() - { - $data = ['foo' => 'bar'.chr(233)]; - $this->assertEquals('bar'.chr(233), $data['foo']); - - $response = new Response(); - $response->withJson($data, 200); - - // Safety net: this assertion should not occur, since the RuntimeException - // must have been caught earlier by the test framework - $this->assertFalse(true); - } -} diff --git a/tests/StreamTest.php b/tests/StreamTest.php deleted file mode 100644 index 562027d..0000000 --- a/tests/StreamTest.php +++ /dev/null @@ -1,159 +0,0 @@ -pipeFh != null) { - stream_get_contents($this->pipeFh); // prevent broken pipe error message - } - } - - /** - * @covers Slim\Http\Stream::isPipe - */ - public function testIsPipe() - { - $this->openPipeStream(); - - $this->assertTrue($this->pipeStream->isPipe()); - - $this->pipeStream->detach(); - $this->assertFalse($this->pipeStream->isPipe()); - - $fhFile = fopen(__FILE__, 'r'); - $fileStream = new Stream($fhFile); - $this->assertFalse($fileStream->isPipe()); - } - - /** - * @covers Slim\Http\Stream::isReadable - */ - public function testIsPipeReadable() - { - $this->openPipeStream(); - - $this->assertTrue($this->pipeStream->isReadable()); - } - - /** - * @covers Slim\Http\Stream::isSeekable - */ - public function testPipeIsNotSeekable() - { - $this->openPipeStream(); - - $this->assertFalse($this->pipeStream->isSeekable()); - } - - /** - * @covers Slim\Http\Stream::seek - * @expectedException \RuntimeException - */ - public function testCannotSeekPipe() - { - $this->openPipeStream(); - - $this->pipeStream->seek(0); - } - - /** - * @covers Slim\Http\Stream::tell - * @expectedException \RuntimeException - */ - public function testCannotTellPipe() - { - $this->openPipeStream(); - - $this->pipeStream->tell(); - } - - /** - * @covers Slim\Http\Stream::rewind - * @expectedException \RuntimeException - */ - public function testCannotRewindPipe() - { - $this->openPipeStream(); - - $this->pipeStream->rewind(); - } - - /** - * @covers Slim\Http\Stream::getSize - */ - public function testPipeGetSizeYieldsNull() - { - $this->openPipeStream(); - - $this->assertNull($this->pipeStream->getSize()); - } - - /** - * @covers Slim\Http\Stream::close - */ - public function testClosePipe() - { - $this->openPipeStream(); - - stream_get_contents($this->pipeFh); // prevent broken pipe error message - $this->pipeStream->close(); - $this->pipeFh = null; - - $this->assertFalse($this->pipeStream->isPipe()); - } - - /** - * @covers Slim\Http\Stream::__toString - */ - public function testPipeToString() - { - $this->openPipeStream(); - - $this->assertSame('', (string) $this->pipeStream); - } - - /** - * @covers Slim\Http\Stream::getContents - */ - - public function testPipeGetContents() - { - $this->openPipeStream(); - - $contents = trim($this->pipeStream->getContents()); - $this->assertSame('12', $contents); - } - - /** - * Opens the pipe stream - * - * @see StreamTest::pipeStream - */ - private function openPipeStream() - { - $this->pipeFh = popen('echo 12', 'r'); - $this->pipeStream = new Stream($this->pipeFh); - } -} diff --git a/tests/UploadedFilesTest.php b/tests/UploadedFilesTest.php deleted file mode 100644 index 8d84588..0000000 --- a/tests/UploadedFilesTest.php +++ /dev/null @@ -1,487 +0,0 @@ -assertEquals($expected, $uploadedFile); - } - - /** - * @param array $input The input array to parse. - * - * @dataProvider providerCreateFromGlobals - */ - public function testCreateFromGlobalsFromUserData(array $input) - { - //If slim.files provided - it will return what was provided - $userData['slim.files'] = $input; - - $uploadedFile = UploadedFile::createFromGlobals(Environment::mock($userData)); - $this->assertEquals($input, $uploadedFile); - } - - public function testCreateFromGlobalsWithoutFile() - { - unset($_FILES); - - $uploadedFile = UploadedFile::createFromGlobals(Environment::mock()); - $this->assertEquals([], $uploadedFile); - } - - /** - * @return UploadedFile - */ - public function testConstructor() - { - $attr = [ - 'tmp_name' => self::$filename, - 'name' => 'my-avatar.txt', - 'size' => 8, - 'type' => 'text/plain', - 'error' => 0, - ]; - - $uploadedFile = new UploadedFile( - $attr['tmp_name'], - $attr['name'], - $attr['type'], - $attr['size'], - $attr['error'], - false - ); - - - $this->assertEquals($attr['name'], $uploadedFile->getClientFilename()); - $this->assertEquals($attr['type'], $uploadedFile->getClientMediaType()); - $this->assertEquals($attr['size'], $uploadedFile->getSize()); - $this->assertEquals($attr['error'], $uploadedFile->getError()); - - return $uploadedFile; - } - - /** - * @depends testConstructor - * @param UploadedFile $uploadedFile - * @return UploadedFile - */ - public function testGetStream(UploadedFile $uploadedFile) - { - $stream = $uploadedFile->getStream(); - $this->assertEquals(true, $uploadedFile->getStream() instanceof Stream); - $stream->close(); - - return $uploadedFile; - } - - /** - * @depends testConstructor - * @param UploadedFile $uploadedFile - * @expectedException \InvalidArgumentException - */ - public function testMoveToNotWritable(UploadedFile $uploadedFile) - { - $tempName = uniqid('file-'); - $path = 'some_random_dir' . DIRECTORY_SEPARATOR . $tempName; - $uploadedFile->moveTo($path); - } - - /** - * @depends testConstructor - * @param UploadedFile $uploadedFile - * @return UploadedFile - */ - public function testMoveTo(UploadedFile $uploadedFile) - { - $tempName = uniqid('file-'); - $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; - $uploadedFile->moveTo($path); - - $this->assertFileExists($path); - - unlink($path); - - return $uploadedFile; - } - - /** - * @depends testMoveTo - * @param UploadedFile $uploadedFile - * @expectedException \RuntimeException - */ - public function testMoveToCannotBeDoneTwice(UploadedFile $uploadedFile) - { - $tempName = uniqid('file-'); - $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; - $uploadedFile->moveTo($path); - $this->assertFileExists($path); - unlink($path); - - $uploadedFile->moveTo($path); - } - - /** - * This test must run after testMoveTo - * - * @depends testConstructor - * @param UploadedFile $uploadedFile - * @expectedException \RuntimeException - */ - public function testMoveToAgain(UploadedFile $uploadedFile) - { - $tempName = uniqid('file-'); - $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; - $uploadedFile->moveTo($path); - } - - /** - * This test must run after testMoveTo - * - * @depends testConstructor - * @param UploadedFile $uploadedFile - * @expectedException \RuntimeException - */ - public function testMovedStream($uploadedFile) - { - $uploadedFile->getStream(); - } - - public function testMoveToStream() - { - $uploadedFile = $this->generateNewTmpFile(); - $uploadedFile->moveTo('php://temp'); - - $this->addToAssertionCount(1); // does not throw an exception - } - - public function providerCreateFromGlobals() - { - return [ - // no nest: - [ - // $_FILES array - [ - 'avatar' => [ - 'tmp_name' => 'phpUxcOty', - 'name' => 'my-avatar.png', - 'size' => 90996, - 'type' => 'image/png', - 'error' => 0, - ], - ], - // expected format of array - [ - 'avatar' => new UploadedFile('phpUxcOty', 'my-avatar.png', 'image/png', 90996, UPLOAD_ERR_OK, true) - ] - ], - // no nest, with error: - [ - // $_FILES array - [ - 'avatar' => [ - 'tmp_name' => 'phpUxcOty', - 'name' => 'my-avatar.png', - 'size' => 90996, - 'type' => 'image/png', - 'error' => 7, - ], - ], - // expected format of array - [ - 'avatar' => new UploadedFile( - 'phpUxcOty', - 'my-avatar.png', - 'image/png', - 90996, - UPLOAD_ERR_CANT_WRITE, - true - ) - ] - ], - - // array of files: - [ - // $_FILES array - [ - 'avatars' => [ - 'tmp_name' => [ - 0 => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - 1 => __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', - ], - 'name' => [ - 0 => 'file0.txt', - 1 => 'file1.html', - ], - 'type' => [ - 0 => 'text/plain', - 1 => 'text/html', - ], - 'error' => [ - 0 => 0, - 1 => 0 - ], - 'size' => [ - 0 => 0, - 1 => 0 - ] - ], - ], - // expected format of array - [ - 'avatars' => [ - 0 => new UploadedFile( - __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - 'file0.txt', - 'text/plain', - null, - UPLOAD_ERR_OK, - true - ), - 1 => new UploadedFile( - __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', - 'file1.html', - 'text/html', - null, - UPLOAD_ERR_OK, - true - ), - ], - ] - ], - // array of files as multidimensional array: - [ - // $_FILES array - [ - [ - 'avatars' => [ - 'tmp_name' => [ - 0 => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - 1 => __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', - ], - 'name' => [ - 0 => 'file0.txt', - 1 => 'file1.html', - ], - 'type' => [ - 0 => 'text/plain', - 1 => 'text/html', - ], - 'size' => [ - 0 => 0, - 1 => 0, - ], - ], - ], - ], - // expected format of array - [ - 0 => - [ - 'avatars' => - [ - 'tmp_name' => [], - 'name' => [], - 'type' => [], - 'size' => [], - ], - ], - ], - ], - // single nested file: - [ - // $_FILES array - [ - 'details' => [ - 'tmp_name' => [ - 'avatar' => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - ], - 'name' => [ - 'avatar' => 'file0.txt', - ], - 'type' => [ - 'avatar' => 'text/plain', - ], - 'error' => [ - 'avatar' => 0, - ], - 'size' => [ - 'avatar' => 0, - ], - ], - ], - // expected format of array - [ - 'details' => [ - 'avatar' => new UploadedFile( - __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - 'file0.txt', - 'text/plain', - null, - UPLOAD_ERR_OK, - true - ), - ], - ] - ], - // nested array of files: - [ - [ - 'files' => [ - 'tmp_name' => [ - 'details' => [ - 'avatar' => [ - 0 => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - 1 => __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', - ], - ], - ], - 'name' => [ - 'details' => [ - 'avatar' => [ - 0 => 'file0.txt', - 1 => 'file1.html', - ], - ], - ], - 'type' => [ - 'details' => [ - 'avatar' => [ - 0 => 'text/plain', - 1 => 'text/html', - ], - ], - ], - 'error' => [ - 'details' => [ - 'avatar' => [ - 0 => 0, - 1 => 0 - ], - ], - ], - 'size' => [ - 'details' => [ - 'avatar' => [ - 0 => 0, - 1 => 0 - ], - ], - ], - ], - ], - // expected format of array - [ - 'files' => [ - 'details' => [ - 'avatar' => [ - 0 => new UploadedFile( - __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', - 'file0.txt', - 'text/plain', - null, - UPLOAD_ERR_OK, - true - ), - 1 => new UploadedFile( - __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', - 'file1.html', - 'text/html', - null, - UPLOAD_ERR_OK, - true - ), - ], - ], - ], - ] - ], - ]; - } - - /** - * @param array $mockEnv An array representing a mock environment. - * - * @return Request - */ - public function requestFactory(array $mockEnv) - { - $env = Environment::mock(); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = Headers::createFromGlobals($env); - $cookies = []; - $serverParams = $env->all(); - $body = new RequestBody(); - $uploadedFiles = UploadedFile::createFromGlobals($env); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - return $request; - } -} diff --git a/tests/UriTest.php b/tests/UriTest.php deleted file mode 100644 index 0609169..0000000 --- a/tests/UriTest.php +++ /dev/null @@ -1,612 +0,0 @@ -assertEquals('https', $this->uriFactory()->getScheme()); - } - - public function testWithScheme() - { - $uri = $this->uriFactory()->withScheme('http'); - - $this->assertAttributeEquals('http', 'scheme', $uri); - } - - public function testWithSchemeRemovesSuffix() - { - $uri = $this->uriFactory()->withScheme('http://'); - - $this->assertAttributeEquals('http', 'scheme', $uri); - } - - public function testWithSchemeEmpty() - { - $uri = $this->uriFactory()->withScheme(''); - - $this->assertAttributeEquals('', 'scheme', $uri); - } - - /** - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Uri scheme must be one of: "", "https", "http" - */ - public function testWithSchemeInvalid() - { - $this->uriFactory()->withScheme('ftp'); - } - - /** - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Uri scheme must be a string - */ - public function testWithSchemeInvalidType() - { - $this->uriFactory()->withScheme([]); - } - - /******************************************************************************** - * Authority - *******************************************************************************/ - - public function testGetAuthorityWithUsernameAndPassword() - { - $this->assertEquals('josh:sekrit@example.com', $this->uriFactory()->getAuthority()); - } - - public function testGetAuthorityWithUsername() - { - $uri = Uri::createFromString('https://josh@example.com/foo/bar?abc=123#section3'); - - $this->assertEquals('josh@example.com', $uri->getAuthority()); - } - - public function testGetAuthority() - { - $uri = Uri::createFromString('https://example.com/foo/bar?abc=123#section3'); - - $this->assertEquals('example.com', $uri->getAuthority()); - } - - public function testGetAuthorityWithNonStandardPort() - { - $uri = Uri::createFromString('https://example.com:400/foo/bar?abc=123#section3'); - - $this->assertEquals('example.com:400', $uri->getAuthority()); - } - - public function testGetUserInfoWithUsernameAndPassword() - { - $uri = Uri::createFromString('https://josh:sekrit@example.com:443/foo/bar?abc=123#section3'); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - } - - public function testGetUserInfoWithUsernameAndPasswordEncodesCorrectly() - { - $uri = Uri::createFromString('https://bob%40example.com:pass%3Aword@example.com:443/foo/bar?abc=123#section3'); - - $this->assertEquals('bob%40example.com:pass%3Aword', $uri->getUserInfo()); - } - - public function testGetUserInfoWithUsername() - { - $uri = Uri::createFromString('http://josh@example.com/foo/bar?abc=123#section3'); - - $this->assertEquals('josh', $uri->getUserInfo()); - } - - public function testGetUserInfoNone() - { - $uri = Uri::createFromString('https://example.com/foo/bar?abc=123#section3'); - - $this->assertEquals('', $uri->getUserInfo()); - } - - public function testWithUserInfo() - { - $uri = $this->uriFactory()->withUserInfo('bob', 'pass'); - - $this->assertAttributeEquals('bob', 'user', $uri); - $this->assertAttributeEquals('pass', 'password', $uri); - } - - public function testWithUserInfoEncodesCorrectly() - { - $uri = $this->uriFactory()->withUserInfo('bob@example.com', 'pass:word'); - - $this->assertAttributeEquals('bob%40example.com', 'user', $uri); - $this->assertAttributeEquals('pass%3Aword', 'password', $uri); - } - - public function testWithUserInfoRemovesPassword() - { - $uri = $this->uriFactory()->withUserInfo('bob'); - - $this->assertAttributeEquals('bob', 'user', $uri); - $this->assertAttributeEquals('', 'password', $uri); - } - - public function testWithUserInfoRemovesInfo() - { - $uri = $this->uriFactory()->withUserInfo('bob', 'password'); - - $uri = $uri->withUserInfo(''); - $this->assertAttributeEquals('', 'user', $uri); - $this->assertAttributeEquals('', 'password', $uri); - } - - public function testGetHost() - { - $this->assertEquals('example.com', $this->uriFactory()->getHost()); - } - - public function testWithHost() - { - $uri = $this->uriFactory()->withHost('slimframework.com'); - - $this->assertAttributeEquals('slimframework.com', 'host', $uri); - } - - public function testFilterHost() - { - $uri = new Uri('http', '2001:db8::1'); - - $this->assertEquals('[2001:db8::1]', $uri->getHost()); - } - - public function testGetPortWithSchemeAndNonDefaultPort() - { - $uri = new Uri('https', 'www.example.com', 4000); - - $this->assertEquals(4000, $uri->getPort()); - } - - public function testGetPortWithSchemeAndDefaultPort() - { - $uriHttp = new Uri('http', 'www.example.com', 80); - $uriHttps = new Uri('https', 'www.example.com', 443); - - $this->assertNull($uriHttp->getPort()); - $this->assertNull($uriHttps->getPort()); - } - - public function testGetPortWithoutSchemeAndPort() - { - $uri = new Uri('', 'www.example.com'); - - $this->assertNull($uri->getPort()); - } - - public function testGetPortWithSchemeWithoutPort() - { - $uri = new Uri('http', 'www.example.com'); - - $this->assertNull($uri->getPort()); - } - - public function testWithPort() - { - $uri = $this->uriFactory()->withPort(8000); - - $this->assertAttributeEquals(8000, 'port', $uri); - } - - public function testWithPortNull() - { - $uri = $this->uriFactory()->withPort(null); - - $this->assertAttributeEquals(null, 'port', $uri); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testWithPortInvalidInt() - { - $this->uriFactory()->withPort(70000); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testWithPortInvalidString() - { - $this->uriFactory()->withPort('Foo'); - } - - /******************************************************************************** - * Path - *******************************************************************************/ - - public function testGetPath() - { - $this->assertEquals('/foo/bar', $this->uriFactory()->getPath()); - } - - public function testWithPath() - { - $uri = $this->uriFactory()->withPath('/new'); - - $this->assertAttributeEquals('/new', 'path', $uri); - } - - public function testWithPathWithoutPrefix() - { - $uri = $this->uriFactory()->withPath('new'); - - $this->assertAttributeEquals('new', 'path', $uri); - } - - public function testWithPathEmptyValue() - { - $uri = $this->uriFactory()->withPath(''); - - $this->assertAttributeEquals('', 'path', $uri); - } - - public function testWithPathUrlEncodesInput() - { - $uri = $this->uriFactory()->withPath('/includes?/new'); - - $this->assertAttributeEquals('/includes%3F/new', 'path', $uri); - } - - public function testWithPathDoesNotDoubleEncodeInput() - { - $uri = $this->uriFactory()->withPath('/include%25s/new'); - - $this->assertAttributeEquals('/include%25s/new', 'path', $uri); - } - - /** - * @covers Slim\Http\Uri::withPath - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Uri path must be a string - */ - public function testWithPathInvalidType() - { - $this->uriFactory()->withPath(['foo']); - } - - /******************************************************************************** - * Query - *******************************************************************************/ - - public function testGetQuery() - { - $this->assertEquals('abc=123', $this->uriFactory()->getQuery()); - } - - public function testWithQuery() - { - $uri = $this->uriFactory()->withQuery('xyz=123'); - - $this->assertAttributeEquals('xyz=123', 'query', $uri); - } - - public function testWithQueryRemovesPrefix() - { - $uri = $this->uriFactory()->withQuery('?xyz=123'); - - $this->assertAttributeEquals('xyz=123', 'query', $uri); - } - - public function testWithQueryEmpty() - { - $uri = $this->uriFactory()->withQuery(''); - - $this->assertAttributeEquals('', 'query', $uri); - } - - public function testFilterQuery() - { - $uri = $this->uriFactory()->withQuery('?foobar=%match'); - - $this->assertAttributeEquals('foobar=%25match', 'query', $uri); - } - - /** - * @covers Slim\Http\Uri::withQuery - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Uri query must be a string - */ - public function testWithQueryInvalidType() - { - $this->uriFactory()->withQuery(['foo']); - } - - /******************************************************************************** - * Fragment - *******************************************************************************/ - - public function testGetFragment() - { - $this->assertEquals('section3', $this->uriFactory()->getFragment()); - } - - public function testWithFragment() - { - $uri = $this->uriFactory()->withFragment('other-fragment'); - - $this->assertAttributeEquals('other-fragment', 'fragment', $uri); - } - - public function testWithFragmentRemovesPrefix() - { - $uri = $this->uriFactory()->withFragment('#other-fragment'); - - $this->assertAttributeEquals('other-fragment', 'fragment', $uri); - } - - public function testWithFragmentEmpty() - { - $uri = $this->uriFactory()->withFragment(''); - - $this->assertAttributeEquals('', 'fragment', $uri); - } - - /** - * @covers Slim\Http\Uri::withFragment - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Uri fragment must be a string - */ - public function testWithFragmentInvalidType() - { - $this->uriFactory()->withFragment(['foo']); - } - - /******************************************************************************** - * Helpers - *******************************************************************************/ - - public function testToString() - { - $uri = $this->uriFactory(); - - $this->assertEquals('https://josh:sekrit@example.com/foo/bar?abc=123#section3', (string) $uri); - - $uri = $uri->withPath('bar'); - $this->assertEquals('https://josh:sekrit@example.com/bar?abc=123#section3', (string) $uri); - - $uri = $uri->withPath('/bar'); - $this->assertEquals('https://josh:sekrit@example.com/bar?abc=123#section3', (string) $uri); - } - - /** - * @covers Slim\Http\Uri::createFromString - */ - public function testCreateFromString() - { - $uri = Uri::createFromString('https://example.com:8080/foo/bar?abc=123'); - - $this->assertEquals('https', $uri->getScheme()); - $this->assertEquals('example.com', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - } - - /** - * @covers Slim\Http\Uri::createFromString - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Uri must be a string - */ - public function testCreateFromStringWithInvalidType() - { - Uri::createFromString(['https://example.com:8080/foo/bar?abc=123']); - } - - public function testCreateFromGlobals() - { - $globals = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:8080', - 'SERVER_PORT' => 8080, - ]); - - $uri = Uri::createFromGlobals($globals); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - $this->assertEquals('example.com', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - $this->assertEquals('', $uri->getFragment()); - } - - - public function testCreateFromGlobalWithIPv6HostNoPort() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => '[2001:db8::1]', - 'REMOTE_ADDR' => '2001:db8::1', - 'SERVER_PORT' => 8080, - ]); - $uri = Uri::createFromGlobals($environment); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - $this->assertEquals('[2001:db8::1]', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - $this->assertEquals('', $uri->getFragment()); - } - - public function testCreateFromGlobalsWithIPv6HostWithPort() - { - $globals = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => '[2001:db8::1]:8080', - 'REMOTE_ADDR' => '2001:db8::1', - 'SERVER_PORT' => 8080, - ]); - - $uri = Uri::createFromGlobals($globals); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - $this->assertEquals('[2001:db8::1]', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - $this->assertEquals('', $uri->getFragment()); - } - - public function testCreateFromGlobalsWithBasePathContainingSpace() - { - $globals = Environment::mock([ - 'SCRIPT_NAME' => "/f'oo bar/index.php", - 'REQUEST_URI' => "/f%27oo%20bar/baz", - ]); - $uri = Uri::createFromGlobals($globals); - - $this->assertEquals('/f%27oo%20bar/baz', $uri->getPath()); - } - - public function testGetBaseUrl() - { - $globals = Environment::mock([ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/bar', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:80', - 'SERVER_PORT' => 80 - ]); - $uri = Uri::createFromGlobals($globals); - - $this->assertEquals('http://example.com', $uri->getBaseUrl()); - } - - public function testGetBaseUrlWithNoBasePath() - { - $globals = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:80', - 'SERVER_PORT' => 80 - ]); - $uri = Uri::createFromGlobals($globals); - - $this->assertEquals('http://example.com', $uri->getBaseUrl()); - } - - public function testGetBaseUrlWithAuthority() - { - $globals = Environment::mock([ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:8080', - 'SERVER_PORT' => 8080 - ]); - $uri = Uri::createFromGlobals($globals); - - $this->assertEquals('http://josh:sekrit@example.com:8080', $uri->getBaseUrl()); - } - - /** - * @covers Slim\Http\Uri::createFromGlobals - * @ticket 1380 - */ - public function testWithPathWhenBaseRootIsEmpty() - { - $globals = \Slim\Http\Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/bar', - ]); - $uri = \Slim\Http\Uri::createFromGlobals($globals); - - $this->assertEquals('http://localhost/test', (string) $uri->withPath('test')); - } - - /** - * When the URL is /foo/index.php/bar/baz, we need the baseURL to be - * /foo/index.php so that routing works correctly. - * - * @ticket 1639 as a fix to 1590 broke this. - */ - public function testRequestURIContainsIndexDotPhp() - { - $uri = Uri::createFromGlobals( - Environment::mock( - [ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/index.php/bar/baz', - ] - ) - ); - $this->assertSame('/foo/index.php/bar/baz', $uri->getPath()); - } - - public function testRequestURICanContainParams() - { - $uri = Uri::createFromGlobals( - Environment::mock( - [ - 'REQUEST_URI' => '/foo?abc=123', - ] - ) - ); - $this->assertEquals('abc=123', $uri->getQuery()); - } - - public function testUriDistinguishZeroFromEmptyString() - { - $expected = 'https://0:0@0:1/0?0#0'; - $this->assertSame($expected, (string) Uri::createFromString($expected)); - } - - public function testGetBaseUrlDistinguishZeroFromEmptyString() - { - $expected = 'https://0:0@0:1/0?0#0'; - $this->assertSame('https://0:0@0:1', (string) Uri::createFromString($expected)->getBaseUrl()); - } -} diff --git a/tests/getallheaders.php b/tests/getallheaders.php deleted file mode 100644 index b1b188e..0000000 --- a/tests/getallheaders.php +++ /dev/null @@ -1,7 +0,0 @@ - 'electrolytes']; - } -} From a7c10c586d8e03f7bf06863fc6154825261ea1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:10:23 -0600 Subject: [PATCH 04/29] add ResponseDecorator, ServerRequestDecorator and UriDecorator --- src/Decorators/ResponseDecorator.php | 550 +++++++++++ src/Decorators/ServerRequestDecorator.php | 1100 +++++++++++++++++++++ src/Decorators/UriDecorator.php | 418 ++++++++ 3 files changed, 2068 insertions(+) create mode 100644 src/Decorators/ResponseDecorator.php create mode 100644 src/Decorators/ServerRequestDecorator.php create mode 100644 src/Decorators/UriDecorator.php diff --git a/src/Decorators/ResponseDecorator.php b/src/Decorators/ResponseDecorator.php new file mode 100644 index 0000000..96a14fd --- /dev/null +++ b/src/Decorators/ResponseDecorator.php @@ -0,0 +1,550 @@ +response = $response; + $this->streamFactory = $streamFactory; + } + + /** + * Disable magic setter to ensure immutability + * @param $name + * @param $value + */ + public function __set($name, $value) + { + } + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody() + { + return $this->response->getBody(); + } + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name) + { + return $this->response->getHeader($name); + } + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name) + { + return $this->response->getHeaderLine($name); + } + + /** + * Retrieves all message header values. + * + * The keys represent the header name as it will be sent over the wire, and + * each value is an array of strings associated with the header. + * + * // Represent the headers as a string + * foreach ($message->getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders() + { + return $this->response->getHeaders(); + } + + /** + * Retrieves the HTTP protocol version as a string. + * + * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). + * + * @return string HTTP protocol version. + */ + public function getProtocolVersion() + { + return $this->response->getProtocolVersion(); + } + + /** + * Gets the response reason phrase associated with 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 Reason phrase; must return an empty string if none present. + */ + public function getReasonPhrase() + { + return $this->response->getReasonPhrase(); + } + + /** + * Gets the response status code. + * + * The status code is a 3-digit integer result code of the server's attempt + * to understand and satisfy the request. + * + * @return int Status code. + */ + public function getStatusCode() + { + return $this->response->getStatusCode(); + } + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name) + { + return $this->response->hasHeader($name); + } + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return ResponseDecorator + * @throws InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value) + { + $response = $this->response->withAddedHeader($name, $value); + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * 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 + * new body stream. + * + * @param StreamInterface $body Body. + * @return ResponseDecorator + * @throws InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body) + { + $response = $this->response->withBody($body); + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return ResponseDecorator + * @throws InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value) + { + $response = $this->response->withHeader($name, $value); + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return ResponseDecorator + */ + public function withoutHeader($name) + { + $response = $this->response->withoutHeader($name); + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Return an instance with the specified HTTP protocol version. + * + * The version string MUST contain only the HTTP version number (e.g., + * "1.1", "1.0"). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new protocol version. + * + * @param string $version HTTP protocol version + * @return ResponseDecorator + */ + public function withProtocolVersion($version) + { + $response = $this->response->withProtocolVersion($version); + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Return an instance with the specified status code and, optionally, reason phrase. + * + * 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 an 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 int $code The 3-digit integer result code to set. + * @param 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 ResponseDecorator + * @throws InvalidArgumentException For invalid status code arguments. + */ + public function withStatus($code, $reasonPhrase = '') + { + $response = $this->response->withStatus($code, $reasonPhrase); + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Json. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Json + * response to the client. + * + * @param mixed $data The data + * @param int $status The HTTP status code. + * @param int $options Json encoding options + * @param int $depth Json encoding max depth + * @return ResponseDecorator + */ + public function withJson($data, int $status = null, int $options = 0, int $depth = 512): ResponseInterface + { + $json = json_encode($data, $options, $depth); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException(json_last_error_msg(), json_last_error()); + } + + $response = $this + ->withHeader('Content-Type', 'application/json;charset=utf-8') + ->withBody($this->streamFactory->createStream($json)); + + if ($status !== null) { + $response = $response->withStatus($status); + } + + $clone = clone $response; + return new ResponseDecorator($clone, $this->streamFactory); + } + + /** + * Redirect. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Redirect + * response to the client. + * + * @param string $url The redirect destination. + * @param int|null $status The redirect HTTP status code. + * @return ResponseDecorator + */ + public function withRedirect(string $url, $status = null): ResponseInterface + { + $response = $this->withHeader('Location', (string) $url); + + if (!is_null($status)) { + return $response->withStatus($status); + } + + return $response->withStatus(302); + } + + /** + * Write data to the response body. + * + * Note: This method is not part of the PSR-7 standard. + * + * Proxies to the underlying stream and writes the provided data to it. + * + * @param string $data + * @return self + */ + public function write($data) + { + $this->getBody()->write($data); + return $this; + } + + /** + * Is this response a client error? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isClientError(): bool + { + return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; + } + + /** + * Is this response empty? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isEmpty(): bool + { + return in_array($this->getStatusCode(), [204, 205, 304]); + } + + /** + * Is this response forbidden? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + * @api + */ + public function isForbidden(): bool + { + return $this->getStatusCode() === 403; + } + + /** + * Is this response informational? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isInformational(): bool + { + return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; + } + + /** + * Is this response OK? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isOk(): bool + { + return $this->getStatusCode() === 200; + } + + /** + * Is this response not Found? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isNotFound(): bool + { + return $this->getStatusCode() === 404; + } + + /** + * Is this response a redirect? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isRedirect(): bool + { + return in_array($this->getStatusCode(), [301, 302, 303, 307, 308]); + } + + /** + * Is this response a redirection? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isRedirection(): bool + { + return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; + } + + /** + * Is this response a server error? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isServerError(): bool + { + return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; + } + + /** + * Is this response successful? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isSuccessful(): bool + { + return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; + } + + /** + * Convert response to string. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string + */ + public function __toString(): string + { + $output = sprintf( + 'HTTP/%s %s %s%s', + $this->getProtocolVersion(), + $this->getStatusCode(), + $this->getReasonPhrase(), + self::EOL + ); + + foreach ($this->getHeaders() as $name => $values) { + $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)) . self::EOL; + } + + $output .= self::EOL; + $output .= (string) $this->getBody(); + + return $output; + } +} diff --git a/src/Decorators/ServerRequestDecorator.php b/src/Decorators/ServerRequestDecorator.php new file mode 100644 index 0000000..f8b7309 --- /dev/null +++ b/src/Decorators/ServerRequestDecorator.php @@ -0,0 +1,1100 @@ +serverRequest = $serverRequest; + + $this->registerMediaTypeParser('application/json', function ($input) { + $result = json_decode($input, true); + if (!is_array($result)) { + return null; + } + return $result; + }); + + $this->registerMediaTypeParser('application/xml', function ($input) { + $backup = libxml_disable_entity_loader(true); + $backup_errors = libxml_use_internal_errors(true); + $result = simplexml_load_string($input); + libxml_disable_entity_loader($backup); + libxml_clear_errors(); + libxml_use_internal_errors($backup_errors); + if ($result === false) { + return null; + } + return $result; + }); + + $this->registerMediaTypeParser('text/xml', function ($input) { + $backup = libxml_disable_entity_loader(true); + $backup_errors = libxml_use_internal_errors(true); + $result = simplexml_load_string($input); + libxml_disable_entity_loader($backup); + libxml_clear_errors(); + libxml_use_internal_errors($backup_errors); + if ($result === false) { + return null; + } + return $result; + }); + + $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) { + parse_str($input, $data); + return $data; + }); + } + + /** + * Disable magic setter to ensure immutability + * @param $name + * @param $value + */ + public function __set($name, $value) + { + } + + /** + * 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) + { + return $this->serverRequest->getAttribute($name, $default); + } + + /** + * 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->serverRequest->getAttributes(); + } + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody() + { + return $this->serverRequest->getBody(); + } + + /** + * 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->serverRequest->getCookieParams(); + } + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name) + { + return $this->serverRequest->getHeader($name); + } + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name) + { + return $this->serverRequest->getHeaderLine($name); + } + + /** + * Retrieves all message header values. + * + * The keys represent the header name as it will be sent over the wire, and + * each value is an array of strings associated with the header. + * + * // Represent the headers as a string + * foreach ($message->getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders() + { + return $this->serverRequest->getHeaders(); + } + + /** + * Retrieves the HTTP method of the request. + * + * @return string Returns the request method. + */ + public function getMethod() + { + return $this->serverRequest->getMethod(); + } + + /** + * 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() + { + $parsedBody = $this->serverRequest->getParsedBody(); + + if ($parsedBody !== null) { + return $parsedBody; + } + + $mediaType = $this->getMediaType(); + + // look for a media type with a structured syntax suffix (RFC 6839) + $parts = explode('+', $mediaType); + if (count($parts) >= 2) { + $mediaType = 'application/' . $parts[count($parts)-1]; + } + + if (isset($this->bodyParsers[$mediaType]) === true) { + $body = (string) $this->getBody(); + $parsed = $this->bodyParsers[$mediaType]($body); + + if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) { + throw new RuntimeException( + 'Request body media type parser return value must be an array, an object, or null' + ); + } + + return $parsed; + } + + return null; + } + + /** + * Retrieves the HTTP protocol version as a string. + * + * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). + * + * @return string HTTP protocol version. + */ + public function getProtocolVersion() + { + return $this->serverRequest->getProtocolVersion(); + } + + /** + * Retrieve query string arguments. + * + * Retrieves the deserialized query string arguments, if any. + * + * Note: the query params might not be in sync with the URI or server + * params. If you need to ensure you are only getting the original + * values, you may need to parse the query string from `getUri()->getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams() + { + $queryParams = $this->serverRequest->getQueryParams(); + + if (is_array($queryParams) && !empty($queryParams)) { + return $queryParams; + } + + $parsedQueryParams = []; + parse_str($this->serverRequest->getUri()->getQuery(), $parsedQueryParams); + + return $parsedQueryParams; + } + + /** + * Retrieves the message's request target. + * + * Retrieves the message's request-target either as it will appear (for + * clients), as it appeared at request (for servers), or as it was + * specified for the instance (see withRequestTarget()). + * + * In most cases, this will be the origin-form of the composed URI, + * unless a value was provided to the concrete implementation (see + * withRequestTarget() below). + * + * If no URI is available, and no request-target has been specifically + * provided, this method MUST return the string "/". + * + * @return string + */ + public function getRequestTarget() + { + return $this->serverRequest->getRequestTarget(); + } + + /** + * 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->serverRequest->getServerParams(); + } + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles() + { + return $this->serverRequest->getUploadedFiles(); + } + + /** + * Retrieves the URI instance. + * + * This method MUST return a UriInterface instance. + * + * @link http://tools.ietf.org/html/rfc3986#section-4.3 + * @return UriInterface Returns a UriInterface instance + * representing the URI of the request. + */ + public function getUri() + { + return $this->serverRequest->getUri(); + } + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name) + { + return $this->serverRequest->hasHeader($name); + } + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return ServerRequestDecorator + * @throws InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value) + { + $serverRequest = $this->serverRequest->withAddedHeader($name, $value); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an 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 an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return ServerRequestDecorator + */ + public function withAttribute($name, $value) + { + $serverRequest = $this->serverRequest->withAttribute($name, $value); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Create a new instance with the specified derived request attributes. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method allows setting all new derived request attributes 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 attributes. + * + * @param array $attributes New attributes + * @return static + */ + public function withAttributes(array $attributes) + { + $serverRequest = $this->serverRequest; + $clone = clone $serverRequest; + + foreach ($attributes as $attribute => $value) { + $clone = $clone->withAttribute($attribute, $value); + } + + return new ServerRequestDecorator($clone); + } + + /** + * Return an 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 an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return ServerRequestDecorator + */ + public function withoutAttribute($name) + { + $serverRequest = $this->serverRequest->withoutAttribute($name); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * 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 + * new body stream. + * + * @param StreamInterface $body Body. + * @return ServerRequestDecorator + * @throws InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body) + { + $serverRequest = $this->serverRequest->withBody($body); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an 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 NOT update the related Cookie header of the request + * instance, nor related 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 an instance that has the + * updated cookie values. + * + * @param array $cookies Array of key/value pairs representing cookies. + * @return ServerRequestDecorator + */ + public function withCookieParams(array $cookies) + { + $serverRequest = $this->serverRequest->withCookieParams($cookies); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return ServerRequestDecorator + * @throws InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value) + { + $serverRequest = $this->serverRequest->withHeader($name, $value); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return ServerRequestDecorator + */ + public function withoutHeader($name) + { + $serverRequest = $this->serverRequest->withoutHeader($name); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an instance with the provided HTTP method. + * + * While HTTP method names are typically all uppercase characters, HTTP + * method names are case-sensitive and thus implementations SHOULD NOT + * modify the given string. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * changed request method. + * + * @param string $method Case-sensitive method. + * @return ServerRequestDecorator + * @throws InvalidArgumentException for invalid HTTP methods. + */ + public function withMethod($method) + { + $serverRequest = $this->serverRequest->withMethod($method); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an 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 an 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 ServerRequestDecorator + * @throws InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data) + { + $serverRequest = $this->serverRequest->withParsedBody($data); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an instance with the specified HTTP protocol version. + * + * The version string MUST contain only the HTTP version number (e.g., + * "1.1", "1.0"). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new protocol version. + * + * @param string $version HTTP protocol version + * @return ServerRequestDecorator + */ + public function withProtocolVersion($version) + { + $serverRequest = $this->serverRequest->withProtocolVersion($version); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an 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 URI 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 an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return ServerRequestDecorator + */ + public function withQueryParams(array $query) + { + $serverRequest = $this->serverRequest->withQueryParams($query); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Return an instance with the specific request-target. + * + * If the request needs a non-origin-form request-target — e.g., for + * specifying an absolute-form, authority-form, or asterisk-form — + * this method may be used to create an instance with the specified + * request-target, verbatim. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * changed request target. + * + * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various + * request-target forms allowed in request messages) + * @param mixed $requestTarget + * @return ServerRequestDecorator + */ + public function withRequestTarget($requestTarget) + { + $serverRequest = $this->serverRequest->withRequestTarget($requestTarget); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return ServerRequestDecorator + * @throws InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles) + { + $serverRequest = $this->serverRequest->withUploadedFiles($uploadedFiles); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Returns an instance with the provided URI. + * + * This method MUST update the Host header of the returned request by + * default if the URI contains a host component. If the URI does not + * contain a host component, any pre-existing Host header MUST be carried + * over to the returned request. + * + * You can opt-in to preserving the original state of the Host header by + * setting `$preserveHost` to `true`. When `$preserveHost` is set to + * `true`, this method interacts with the Host header in the following ways: + * + * - If the Host header is missing or empty, and the new URI contains + * a host component, this method MUST update the Host header in the returned + * request. + * - If the Host header is missing or empty, and the new URI does not contain a + * host component, this method MUST NOT update the Host header in the returned + * request. + * - If a Host header is present and non-empty, this method MUST NOT update + * the Host header in the returned request. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new UriInterface instance. + * + * @link http://tools.ietf.org/html/rfc3986#section-4.3 + * @param UriInterface $uri New request URI to use. + * @param bool $preserveHost Preserve the original state of the Host header. + * @return ServerRequestDecorator + */ + public function withUri(UriInterface $uri, $preserveHost = false) + { + $serverRequest = $this->serverRequest->withUri($uri, $preserveHost); + $clone = clone $serverRequest; + return new ServerRequestDecorator($clone); + } + + /** + * Get serverRequest content character set, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null + */ + public function getContentCharset() + { + $mediaTypeParams = $this->getMediaTypeParams(); + if (isset($mediaTypeParams['charset'])) { + return $mediaTypeParams['charset']; + } + + return null; + } + + /** + * Get serverRequest content type. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The serverRequest content type, if known + */ + public function getContentType() + { + $result = $this->getHeader('Content-Type'); + + return $result ? $result[0] : null; + } + + /** + * Get serverRequest content length, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return int|null + */ + public function getContentLength() + { + $result = $this->getHeader('Content-Length'); + + return $result ? (int) $result[0] : null; + } + + /** + * Fetch cookie value from cookies sent by the client to the server. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * + * @return mixed + */ + public function getCookieParam($key, $default = null) + { + $cookies = $this->getCookieParams(); + $result = $default; + if (isset($cookies[$key])) { + $result = $cookies[$key]; + } + + return $result; + } + + /** + * Get serverRequest media type, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The serverRequest media type, minus content-type params + */ + public function getMediaType() + { + $contentType = $this->getContentType(); + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + return strtolower($contentTypeParts[0]); + } + + return null; + } + + /** + * Get serverRequest media type params, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return array + */ + public function getMediaTypeParams() + { + $contentType = $this->getContentType(); + $contentTypeParams = []; + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + $contentTypePartsLength = count($contentTypeParts); + for ($i = 1; $i < $contentTypePartsLength; $i++) { + $paramParts = explode('=', $contentTypeParts[$i]); + $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; + } + } + + return $contentTypeParams; + } + + /** + * Fetch serverRequest parameter value from body or query string (in that order). + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key The parameter key. + * @param string $default The default value. + * + * @return mixed The parameter value. + */ + public function getParam($key, $default = null) + { + $postParams = $this->getParsedBody(); + $getParams = $this->getQueryParams(); + $result = $default; + if (is_array($postParams) && isset($postParams[$key])) { + $result = $postParams[$key]; + } elseif (is_object($postParams) && property_exists($postParams, $key)) { + $result = $postParams->$key; + } elseif (isset($getParams[$key])) { + $result = $getParams[$key]; + } + + return $result; + } + + /** + * Fetch associative array of body and query string parameters. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return array + */ + public function getParams() + { + $params = $this->getQueryParams(); + $postParams = $this->getParsedBody(); + if ($postParams) { + $params = array_merge($params, (array)$postParams); + } + + return $params; + } + + /** + * Fetch parameter value from serverRequest body. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getParsedBodyParam($key, $default = null) + { + $postParams = $this->getParsedBody(); + $result = $default; + if (is_array($postParams) && isset($postParams[$key])) { + $result = $postParams[$key]; + } elseif (is_object($postParams) && property_exists($postParams, $key)) { + $result = $postParams->$key; + } + + return $result; + } + + /** + * Fetch parameter value from query string. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getQueryParam($key, $default = null) + { + $getParams = $this->getQueryParams(); + $result = $default; + if (isset($getParams[$key])) { + $result = $getParams[$key]; + } + + return $result; + } + + /** + * Retrieve a server parameter. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getServerParam($key, $default = null) + { + $serverParams = $this->getServerParams(); + + return isset($serverParams[$key]) ? $serverParams[$key] : $default; + } + + /** + * Register media type parser. + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $mediaType A HTTP media type (excluding content-type params). + * @param callable $callable A callable that returns parsed contents for media type. + * @return self + */ + public function registerMediaTypeParser($mediaType, callable $callable) + { + if ($callable instanceof Closure) { + $callable = $callable->bindTo($this); + } + $this->bodyParsers[$mediaType] = $callable; + + return $this; + } + + /** + * Is this a DELETE serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isDelete() + { + return $this->isMethod('DELETE'); + } + + /** + * Is this a GET serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isGet() + { + return $this->isMethod('GET'); + } + + /** + * Is this a HEAD serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isHead() + { + return $this->isMethod('HEAD'); + } + + /** + * Does this serverRequest use a given method? + * + * Note: This method is not part of the PSR-7 standard. + * + * @param string $method HTTP method + * @return bool + */ + public function isMethod($method) + { + return $this->getMethod() === $method; + } + + /** + * Is this a OPTIONS serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isOptions() + { + return $this->isMethod('OPTIONS'); + } + + /** + * Is this a PATCH serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPatch() + { + return $this->isMethod('PATCH'); + } + + /** + * Is this a POST serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPost() + { + return $this->isMethod('POST'); + } + + /** + * Is this a PUT serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isPut() + { + return $this->isMethod('PUT'); + } + + /** + * Is this an XHR serverRequest? + * + * Note: This method is not part of the PSR-7 standard. + * + * @return bool + */ + public function isXhr() + { + return $this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; + } +} diff --git a/src/Decorators/UriDecorator.php b/src/Decorators/UriDecorator.php new file mode 100644 index 0000000..59ef40b --- /dev/null +++ b/src/Decorators/UriDecorator.php @@ -0,0 +1,418 @@ +uri = $uri; + } + + /** + * Disable magic setter to ensure immutability + * @param $name + * @param $value + */ + public function __set($name, $value) + { + } + + /** + * Retrieve the authority component of the URI. + * + * If no authority information is present, this method MUST return an empty + * string. + * + * The authority syntax of the URI is: + * + *
+     * [user-info@]host[:port]
+     * 
+ * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority() + { + return $this->uri->getAuthority(); + } + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment() + { + return $this->uri->getFragment(); + } + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost() + { + return $this->uri->getHost(); + } + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath() + { + return $this->uri->getPath(); + } + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort() + { + return $this->uri->getPort(); + } + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery() + { + return $this->uri->getQuery(); + } + + /** + * Retrieve the scheme component of the URI. + * + * If no scheme is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.1. + * + * The trailing ":" character is not part of the scheme and MUST NOT be + * added. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.1 + * @return string The URI scheme. + */ + public function getScheme() + { + return $this->uri->getScheme(); + } + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo() + { + return $this->uri->getUserInfo(); + } + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return UriDecorator A new instance with the specified fragment. + */ + public function withFragment($fragment) + { + $uri = $this->uri->withFragment($fragment); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return UriDecorator A new instance with the specified host. + * @throws InvalidArgumentException for invalid hostnames. + */ + public function withHost($host) + { + $uri = $this->uri->withHost($host); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return UriDecorator A new instance with the specified path. + * @throws InvalidArgumentException for invalid paths. + */ + public function withPath($path) + { + $uri = $this->uri->withPath($path); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return UriDecorator A new instance with the specified port. + * @throws InvalidArgumentException for invalid ports. + */ + public function withPort($port) + { + $uri = $this->uri->withPort($port); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return UriDecorator A new instance with the specified query string. + * @throws InvalidArgumentException for invalid query strings. + */ + public function withQuery($query) + { + $uri = $this->uri->withQuery($query); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return UriDecorator A new instance with the specified scheme. + * @throws InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme) + { + $uri = $this->uri->withScheme($scheme); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return UriDecorator A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null) + { + $uri = $this->uri->withUserInfo($user, $password); + $clone = clone $uri; + return new UriDecorator($clone); + } + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString() + { + return $this->uri->__toString(); + } + + /** + * Return the fully qualified base URL. + * + * Note that this method never includes a trailing / + * + * This method is not part of PSR-7. + * + * @return string + */ + public function getBaseUrl() + { + $scheme = $this->getScheme(); + $authority = $this->getAuthority(); + return ($scheme !== '' ? $scheme . ':' : '') . ($authority !== '' ? '//' . $authority : ''); + } +} From 5f6435ebee49108d3f265f61bc005a88b8f1a5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:10:47 -0600 Subject: [PATCH 05/29] add DecoratedResponseFactory, DecoratedServerRequestFactory and DecoratedUriFactory --- src/Factory/DecoratedResponseFactory.php | 53 +++++++++++++++++++ src/Factory/DecoratedServerRequestFactory.php | 46 ++++++++++++++++ src/Factory/DecoratedUriFactory.php | 44 +++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 src/Factory/DecoratedResponseFactory.php create mode 100644 src/Factory/DecoratedServerRequestFactory.php create mode 100644 src/Factory/DecoratedUriFactory.php diff --git a/src/Factory/DecoratedResponseFactory.php b/src/Factory/DecoratedResponseFactory.php new file mode 100644 index 0000000..e199abb --- /dev/null +++ b/src/Factory/DecoratedResponseFactory.php @@ -0,0 +1,53 @@ +responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + /** + * @param int $code + * @param string $reasonPhrase + * @return ResponseDecorator + */ + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + $response = $this->responseFactory->createResponse($code, $reasonPhrase); + return new ResponseDecorator($response, $this->streamFactory); + } +} diff --git a/src/Factory/DecoratedServerRequestFactory.php b/src/Factory/DecoratedServerRequestFactory.php new file mode 100644 index 0000000..c5a7b67 --- /dev/null +++ b/src/Factory/DecoratedServerRequestFactory.php @@ -0,0 +1,46 @@ +serverRequestFactory = $serverRequestFactory; + } + + /** + * @param string $method + * @param \Psr\Http\Message\UriInterface|string $uri + * @param array $serverParams + * @return ServerRequestDecorator + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $serverParams); + return new ServerRequestDecorator($serverRequest); + } +} diff --git a/src/Factory/DecoratedUriFactory.php b/src/Factory/DecoratedUriFactory.php new file mode 100644 index 0000000..f6bc7cc --- /dev/null +++ b/src/Factory/DecoratedUriFactory.php @@ -0,0 +1,44 @@ +uriFactory = $uriFactory; + } + + /** + * @param string $uri + * @return UriDecorator + */ + public function createUri(string $uri = ''): UriInterface + { + $uri = $this->uriFactory->createUri($uri); + return new UriDecorator($uri); + } +} From b410356bdc8b677b4ffcab9747abbf33e553bb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:11:45 -0600 Subject: [PATCH 06/29] add PSR-17 providers to enable testing with two different PSR-7 implementations --- .../Providers/NyholmPsr17FactoryProvider.php | 27 ++++++ tests/Providers/Psr17FactoryProvider.php | 87 +++++++++++++++++++ .../ZendDiactorosPsr17FactoryProvider.php | 34 ++++++++ tests/Test.php | 29 +++++++ 4 files changed, 177 insertions(+) create mode 100644 tests/Providers/NyholmPsr17FactoryProvider.php create mode 100644 tests/Providers/Psr17FactoryProvider.php create mode 100644 tests/Providers/ZendDiactorosPsr17FactoryProvider.php create mode 100644 tests/Test.php diff --git a/tests/Providers/NyholmPsr17FactoryProvider.php b/tests/Providers/NyholmPsr17FactoryProvider.php new file mode 100644 index 0000000..ced687a --- /dev/null +++ b/tests/Providers/NyholmPsr17FactoryProvider.php @@ -0,0 +1,27 @@ +responseFactory = new Psr17Factory(); + $this->serverRequestFactory = new Psr17Factory(); + $this->streamFactory = new Psr17Factory(); + $this->uploadedFileFactory = new Psr17Factory(); + $this->uriFactory = new Psr17Factory(); + } +} diff --git a/tests/Providers/Psr17FactoryProvider.php b/tests/Providers/Psr17FactoryProvider.php new file mode 100644 index 0000000..fd47e84 --- /dev/null +++ b/tests/Providers/Psr17FactoryProvider.php @@ -0,0 +1,87 @@ +responseFactory; + } + + /** + * @return ServerRequestFactoryInterface + */ + public function getServerRequestFactory(): ServerRequestFactoryInterface + { + return $this->serverRequestFactory; + } + + /** + * @return StreamFactoryInterface + */ + public function getStreamFactory(): StreamFactoryInterface + { + return $this->streamFactory; + } + + /** + * @return UploadedFileFactoryInterface + */ + public function getUploadedFileFactory(): UploadedFileFactoryInterface + { + return $this->uploadedFileFactory; + } + + /** + * @return UriFactoryInterface + */ + public function getUriFactory(): UriFactoryInterface + { + return $this->uriFactory; + } +} diff --git a/tests/Providers/ZendDiactorosPsr17FactoryProvider.php b/tests/Providers/ZendDiactorosPsr17FactoryProvider.php new file mode 100644 index 0000000..93baec1 --- /dev/null +++ b/tests/Providers/ZendDiactorosPsr17FactoryProvider.php @@ -0,0 +1,34 @@ +responseFactory = new ResponseFactory(); + $this->serverRequestFactory = new ServerRequestFactory(); + $this->streamFactory = new StreamFactory(); + $this->uploadedFileFactory = new UploadedFileFactory(); + $this->uriFactory = new UriFactory(); + } +} diff --git a/tests/Test.php b/tests/Test.php new file mode 100644 index 0000000..60ba628 --- /dev/null +++ b/tests/Test.php @@ -0,0 +1,29 @@ + Date: Tue, 16 Oct 2018 01:12:27 -0600 Subject: [PATCH 07/29] remove unused declarations in PHP Unit bootstrapper --- tests/bootstrap.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0f56b4a..26a796a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,3 +1,2 @@ Date: Tue, 16 Oct 2018 01:12:46 -0600 Subject: [PATCH 08/29] add supporting tests for all decorators --- tests/Decorators/ResponseDecoratorTest.php | 449 +++++++ .../Decorators/ServerRequestDecoratorTest.php | 1156 +++++++++++++++++ tests/Decorators/UriDecoratorTest.php | 448 +++++++ 3 files changed, 2053 insertions(+) create mode 100644 tests/Decorators/ResponseDecoratorTest.php create mode 100644 tests/Decorators/ServerRequestDecoratorTest.php create mode 100644 tests/Decorators/UriDecoratorTest.php diff --git a/tests/Decorators/ResponseDecoratorTest.php b/tests/Decorators/ResponseDecoratorTest.php new file mode 100644 index 0000000..81d129d --- /dev/null +++ b/tests/Decorators/ResponseDecoratorTest.php @@ -0,0 +1,449 @@ +factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + $response->foo = 'bar'; + + $this->assertFalse(property_exists($response, 'foo')); + } + } + + public function testGetHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + $response = $response->withHeader('Content-Type', 'application/json'); + + $this->assertEquals(['application/json'], $response->getHeader('Content-Type')); + } + } + + public function testGetStatusCode() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(404); + $this->assertEquals(404, $response->getStatusCode()); + } + } + + public function testWithStatus() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + $clone = $response->withStatus(404); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(404, $clone->getStatusCode()); + } + } + + public function testGetReasonPhrase() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(404, 'Not Found'); + $this->assertEquals('Not Found', $response->getReasonPhrase()); + } + } + + public function testGetCustomReasonPhrase() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(200, 'Custom Phrase'); + $this->assertEquals('Custom Phrase', $response->getReasonPhrase()); + } + } + + public function testWithAddedHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + $response = $response->withHeader('Content-Type', 'application/json'); + $response = $response->withAddedHeader('Content-Type', 'application/pdf'); + + $this->assertEquals(['application/json', 'application/pdf'], $response->getHeader('Content-Type')); + } + } + + public function testWithoutHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + + $response = $response->withHeader('Content-Type', 'application/json'); + $this->assertEquals(['application/json'], $response->getHeader('Content-Type')); + + $response = $response->withoutHeader('Content-Type'); + $this->assertEquals([], $response->getHeader('Content-Type')); + } + } + + public function testWithProtocolVersion() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + + $response = $response->withProtocolVersion('1.0'); + $this->assertEquals('1.0', $response->getProtocolVersion()); + } + } + + public function testWithRedirect() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + $clone = $response->withRedirect('/foo', 301); + $cloneWithDefaultStatus = $response->withRedirect('/foo'); + $cloneWithStatusMethod = $response->withStatus(301)->withRedirect('/foo'); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertFalse($response->hasHeader('Location')); + + $this->assertSame(301, $clone->getStatusCode()); + $this->assertTrue($clone->hasHeader('Location')); + $this->assertEquals('/foo', $clone->getHeaderLine('Location')); + + $this->assertSame(302, $cloneWithDefaultStatus->getStatusCode()); + $this->assertTrue($cloneWithDefaultStatus->hasHeader('Location')); + $this->assertEquals('/foo', $cloneWithDefaultStatus->getHeaderLine('Location')); + + $this->assertSame(302, $cloneWithStatusMethod->getStatusCode()); + $this->assertTrue($cloneWithStatusMethod->hasHeader('Location')); + $this->assertEquals('/foo', $cloneWithStatusMethod->getHeaderLine('Location')); + } + } + + public function testIsEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(204); + + $this->assertTrue($response->isEmpty()); + } + } + + public function testIsInformational() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(100); + + $this->assertTrue($response->isInformational()); + } + } + + public function testIsOk() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(); + + $this->assertTrue($response->isOk()); + } + } + + public function testIsSuccessful() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(201); + $this->assertTrue($response->isSuccessful()); + } + } + + public function testIsRedirect() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(302); + $this->assertTrue($response->isRedirect()); + } + } + + public function testIsRedirection() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(308); + $this->assertTrue($response->isRedirection()); + } + } + + public function testIsForbidden() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(403); + $this->assertTrue($response->isForbidden()); + } + } + + public function testIsNotFound() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(404); + $this->assertTrue($response->isNotFound()); + } + } + + public function testIsClientError() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(400); + $this->assertTrue($response->isClientError()); + } + } + + public function testIsServerError() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(503); + $this->assertTrue($response->isServerError()); + } + } + + public function testToString() + { + $output = 'HTTP/1.1 404 Not Found' . ResponseDecorator::EOL . + 'X-Foo: Bar' . ResponseDecorator::EOL . ResponseDecorator::EOL . + 'Where am I?'; + + $expectedOutputString = ''; + $actualOutputString = ''; + + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(308); + $response = $response->withStatus(404)->withHeader('X-Foo', 'Bar')->write('Where am I?'); + + $expectedOutputString .= $output; + $actualOutputString .= (string) $response; + } + + $this->assertEquals($expectedOutputString, $actualOutputString); + } + + public function testWithJson() + { + $data = ['foo' => 'bar1&bar2']; + + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + /** @var ResponseDecorator $originalResponse */ + $originalResponse = $decoratedResponseFactory->createResponse(503); + $response = $originalResponse->withJson($data, 201); + + $this->assertNotEquals($response->getStatusCode(), $originalResponse->getStatusCode()); + $this->assertEquals(201, $response->getStatusCode()); + $this->assertEquals('application/json;charset=utf-8', $response->getHeaderLine('Content-Type')); + + $body = $response->getBody(); + $body->rewind(); + $dataJson = $body->getContents(); //json_decode($body->getContents(), true); + + $originalBody = $originalResponse->getBody(); + $originalBody->rewind(); + $originalContents = $originalBody->getContents(); + + // test the original body hasn't be replaced + $this->assertNotEquals($dataJson, $originalContents); + $this->assertEquals('{"foo":"bar1&bar2"}', $dataJson); + $this->assertEquals($data['foo'], json_decode($dataJson, true)['foo']); + + $response = $response->withJson($data, 200, JSON_HEX_AMP); + + $body = $response->getBody(); + $body->rewind(); + $dataJson = $body->getContents(); + + $this->assertEquals('{"foo":"bar1\u0026bar2"}', $dataJson); + $this->assertEquals($data['foo'], json_decode($dataJson, true)['foo']); + + $response = $response->withStatus(201)->withJson([]); + $this->assertEquals($response->getStatusCode(), 201); + } + } + + /** + * @expectedException \RuntimeException + */ + public function testWithInvalidJsonThrowsException() + { + $data = ['foo' => 'bar'.chr(233)]; + $this->assertEquals('bar'.chr(233), $data['foo']); + + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedResponseFactory = new DecoratedResponseFactory( + $provider->getResponseFactory(), + $provider->getStreamFactory() + ); + + $response = $decoratedResponseFactory->createResponse(400); + $response->withJson($data, 200); + } + } +} diff --git a/tests/Decorators/ServerRequestDecoratorTest.php b/tests/Decorators/ServerRequestDecoratorTest.php new file mode 100644 index 0000000..c6bf00a --- /dev/null +++ b/tests/Decorators/ServerRequestDecoratorTest.php @@ -0,0 +1,1156 @@ +factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $request->foo = 'bar'; + + $this->assertFalse(property_exists($request, 'foo')); + } + } + + public function testAddsHostHeaderFromUri() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'http://example.com'); + $this->assertEquals('example.com', $request->getHeaderLine('Host')); + } + } + + public function testGetMethod() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $this->assertEquals('GET', $request->getMethod()); + } + } + + public function testWithMethod() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $request = $request->withMethod('POST'); + $this->assertEquals('POST', $request->getMethod()); + } + } + + public function testWithMethodCaseSensitive() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $request = $request->withMethod('pOsT'); + $this->assertEquals('pOsT', $request->getMethod()); + } + } + + public function testWithAllAllowedCharactersMethod() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $request = $request->withMethod("!#$%&'*+.^_`|~09AZ-"); + $this->assertEquals("!#$%&'*+.^_`|~09AZ-", $request->getMethod()); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWithMethodInvalid() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $request->withMethod('B@R'); + } + } + + public function testIsGet() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $this->assertEquals(true, $request->isGet()); + } + } + + public function testIsPost() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', '/'); + $this->assertEquals(true, $request->isPost()); + } + } + + public function testIsPut() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('PUT', '/'); + $this->assertEquals(true, $request->isPut()); + } + } + + public function testIsPatch() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('PATCH', '/'); + $this->assertEquals(true, $request->isPatch()); + } + } + + public function testIsDelete() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('DELETE', '/'); + $this->assertEquals(true, $request->isDelete()); + } + } + + public function testIsHead() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('HEAD', '/'); + $this->assertEquals(true, $request->isHead()); + } + } + + public function testIsOptions() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('OPTIONS', '/'); + $this->assertEquals(true, $request->isOptions()); + } + } + + public function testIsXhr() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', '/'); + $request = $request + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withHeader('X-Requested-With', 'XMLHttpRequest'); + + $this->assertEquals(true, $request->isXhr()); + } + } + + public function testGetRequestTarget() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com/foo/bar?abc=123'); + $this->assertEquals('/foo/bar?abc=123', $request->getRequestTarget()); + } + } + + public function testWithRequestTarget() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com/foo/bar?abc=123'); + $request = $request->withRequestTarget('/foo/bar?abc=123'); + + $this->assertEquals('/foo/bar?abc=123', $request->getRequestTarget()); + } + } + + public function testGetUri() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertEquals('https://google.com', $request->getUri()); + } + } + + public function testWithUri() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $uriFactory = $provider->getUriFactory(); + $uri = $uriFactory->createUri('https://example.com'); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withUri($uri); + + $this->assertEquals('https://example.com', $request->getUri()); + } + } + + public function testGetContentType() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json'); + + $this->assertEquals('application/json', $request->getContentType()); + } + } + + public function testGetContentTypeEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertNull($request->getContentType()); + } + } + + public function testGetMediaType() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json'); + + $this->assertEquals('application/json', $request->getMediaType()); + } + } + + public function testGetMediaTypeEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertNull($request->getMediaType()); + } + } + + public function testGetMediaTypeParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json;charset=utf8;foo=bar'); + + $this->assertEquals(['charset' => 'utf8', 'foo' => 'bar'], $request->getMediaTypeParams()); + } + } + + public function testGetMediaTypeParamsEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json'); + + $this->assertEquals([], $request->getMediaTypeParams()); + } + } + + public function testGetMediaTypeParamsWithoutHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertEquals([], $request->getMediaTypeParams()); + } + } + + public function testGetContentCharset() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json;charset=utf8'); + + $this->assertEquals('utf8', $request->getContentCharset()); + } + } + + public function testGetContentCharsetEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json'); + + $this->assertNull($request->getContentCharset()); + } + } + + public function testGetContentCharsetWithoutHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertNull($request->getContentCharset()); + } + } + + public function testGetContentLength() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Length', '150'); + + $this->assertEquals(150, $request->getContentLength()); + } + } + + public function testGetContentLengthWithoutHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + + $this->assertNull($request->getContentLength()); + } + } + + public function testGetCookieParam() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withCookieParams(['user' => 'john']); + + $this->assertEquals('john', $request->getCookieParam('user')); + } + } + + public function testGetCookieParamWithDefault() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertEquals('john', $request->getCookieParam('user', 'john')); + } + } + + public function testGetCookieParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withCookieParams(['user' => 'john', 'password' => '123']); + + $this->assertEquals(['user' => 'john', 'password' => '123'], $request->getCookieParams()); + } + } + + public function testWithCookieParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withCookieParams(['user' => 'john']); + + $this->assertEquals(['user' => 'john'], $request->getCookieParams()); + } + } + + public function testGetQueryParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withQueryParams(['bar' => '123']); + + $this->assertEquals(['bar' => '123'], $request->getQueryParams()); + } + } + + public function testWithQueryParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withQueryParams(['bar' => '123']); + + $this->assertEquals(['bar' => '123'], $request->getQueryParams()); + } + } + + public function testWithQueryParamsEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withQueryParams(['bar' => '123']); + $clone = $request->withQueryParams([]); + + $this->assertEquals(['bar' => '123'], $request->getQueryParams()); + $this->assertEquals([], $clone->getQueryParams()); + } + } + + public function testWithUploadedFiles() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('test'); + + $uploadedFileFactory = $provider->getUploadedFileFactory(); + $files = [$uploadedFileFactory->createUploadedFile($stream, null, 0, 'foo.txt')]; + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $clone = $request->withUploadedFiles($files); + + $this->assertEquals([], $request->getUploadedFiles()); + $this->assertEquals($files, $clone->getUploadedFiles()); + } + } + + public function testGetServerParam() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $serverParams = ['HTTP_AUTHORIZATION' => 'test']; + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com', $serverParams); + + $this->assertEquals('test', $request->getServerParam('HTTP_AUTHORIZATION')); + } + } + + public function testGetServerParamWithDefault() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $this->assertEquals('test', $request->getServerParam('HTTP_AUTHORIZATION', 'test')); + } + } + + public function testGetServerParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $serverParams = ['HTTP_AUTHORIZATION' => 'test']; + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com', $serverParams); + + $this->assertEquals($serverParams, $request->getServerParams()); + } + } + + public function testGetAttribute() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withAttribute('foo', 'bar'); + + $this->assertEquals('bar', $request->getAttribute('foo')); + } + } + + public function testGetAttributes() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request + ->withAttribute('foo', 'bar') + ->withAttribute('bar', 'baz'); + + $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $request->getAttributes()); + } + } + + public function testWithAttribute() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withAttribute('foo', 'bar'); + $clone = $request->withAttribute('foo', 'baz'); + + $this->assertEquals('baz', $clone->getAttribute('foo')); + } + } + + public function testWithAttributes() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withAttributes(['foo' => 'bar', 'bar' => 'baz']); + + $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $request->getAttributes()); + } + } + + public function testWithoutAttribute() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withAttributes(['foo' => 'bar', 'bar' => 'baz']); + $request = $request->withoutAttribute('bar'); + + $this->assertEquals(['foo' => 'bar'], $request->getAttributes()); + } + } + + public function testGetParsedBody() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request->withParsedBody(['foo' => 'bar']); + + $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); + } + } + + public function testGetParsedBodyNull() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request->withParsedBody([]); + $clone = $request->withParsedBody(null); + + $this->assertNull($clone->getParsedBody()); + } + } + + public function testGetParsedBodyForm() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('foo=bar'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf8') + ->withBody($stream); + + $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); + } + } + + public function testGetParsedBodyJson() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('{"foo":"bar"}'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/json;charset=utf8') + ->withBody($stream); + + $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); + } + } + + public function testGetParsedBodyInvalidJson() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('{"foo"}/bar'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/json;charset=utf8') + ->withBody($stream); + + $this->assertNull($request->getParsedBody()); + } + } + + public function testGetParsedBodySemiValidJson() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('"foo bar"'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/json;charset=utf8') + ->withBody($stream); + + $this->assertNull($request->getParsedBody()); + } + } + + public function testGetParsedBodyWithJsonStructuredSuffix() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('{"foo":"bar"}'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/vnd.api+json;charset=utf8') + ->withBody($stream); + + $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); + } + } + + public function testGetParsedBodyXml() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('John'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/hal+xml;charset=utf8') + ->withBody($stream); + + $this->assertEquals('John', $request->getParsedBody()->name); + } + } + + public function testGetParsedBodyStructuredSuffixXml() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('John'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/xml;charset=utf8') + ->withBody($stream); + + $this->assertEquals('John', $request->getParsedBody()->name); + } + } + + public function testGetParsedBodyXmlWithTextXMLMediaType() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('John'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'text/xml') + ->withBody($stream); + + $this->assertEquals('John', $request->getParsedBody()->name); + } + } + + /** + * Will fail if a simple_xml warning is created + */ + public function testInvalidXmlIsQuietForTextXml() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('JohncreateServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'text/xml') + ->withBody($stream); + + $this->assertNull($request->getParsedBody()); + } + } + + /** + * Will fail if a simple_xml warning is created + */ + public function testInvalidXmlIsQuietForApplicationXml() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('JohncreateServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/xml') + ->withBody($stream); + + $this->assertNull($request->getParsedBody()); + } + } + + public function testGetParameterFromBody() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request->withParsedBody(['foo' => 'bar']); + $clone = $request->withParsedBody((object) ['foo' => 'bar']); + + $this->assertEquals('bar', $request->getParam('foo')); + $this->assertEquals('bar', $clone->getParam('foo')); + } + } + + /** + * @expectedException \RuntimeException + */ + public function testGetParsedBodyThrowsRuntimeExceptionWhenInvalidTypeReturned() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('{"foo":"bar"}'); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/json;charset=utf8') + ->withBody($stream); + + $request->registerMediaTypeParser('application/json', function () { + return 10; + }); + + $request->getParsedBody(); + } + } + + public function testWithParsedBody() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request->withParsedBody([]); + + $this->assertEquals([], $request->getParsedBody()); + } + } + + public function testWithParsedBodyNull() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request->withParsedBody(null); + $this->assertNull($request->getParsedBody()); + } + } + + public function testGetParameterFromBodyWithHelper() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('POST', 'https://google.com'); + $request = $request->withParsedBody(['foo' => 'bar']); + $clone = $request->withParsedBody((object) ['foo' => 'bar']); + + $this->assertEquals('bar', $request->getParsedBodyParam('foo')); + $this->assertEquals('bar', $clone->getParsedBodyParam('foo')); + } + } + + public function testGetQueryParam() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com?foo=bar'); + $this->assertEquals('bar', $request->getQueryParam('foo')); + } + } + + public function testGetQueryParamWithGetParam() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com?foo=bar'); + $this->assertEquals('bar', $request->getParam('foo')); + } + } + + public function testGetParams() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com?foo=bar&bar=baz'); + $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $request->getParams()); + } + } + + public function testGetParamsWithBodyPriority() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('foo=bar&bar=baz'); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com?foo=baz&bar=foo'); + $request = $request + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($stream); + + $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $request->getParams()); + } + } + + public function testGetParamFromBodyOverQuery() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('foo=bar'); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com?foo=baz'); + $request = $request + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($stream); + + $this->assertEquals('bar', $request->getParam('foo')); + } + } + + public function testGetParamWithDefaultFromBodyOverQuery() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $streamFactory = $provider->getStreamFactory(); + $stream = $streamFactory->createStream('foo=bar'); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com?foo=baz'); + $request = $request + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($stream); + + $this->assertEquals('baz', $request->getParam('bar', 'baz')); + } + } + + public function testGetProtocolVersion() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withProtocolVersion('1.0'); + + $this->assertEquals('1.0', $request->getProtocolVersion()); + } + } + + public function testWithProtocolVersion() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withProtocolVersion('1.0'); + $clone = $request->withProtocolVersion('1.1'); + + $this->assertEquals('1.0', $request->getProtocolVersion()); + $this->assertEquals('1.1', $clone->getProtocolVersion()); + } + } + + public function testGetHeaders() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json'); + + $expectedHeaders = ['Content-Type' => ['application/json'], 'Host' => ['google.com']]; + $this->assertEquals($expectedHeaders, $request->getHeaders()); + } + } + + public function testHasHeaders() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request->withHeader('Content-Type', 'application/json'); + + $this->assertEquals(true, $request->hasHeader('Content-Type')); + } + } + + public function testWithAddedHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request + ->withHeader('Content-Type', 'application/json') + ->withAddedHeader('Content-Type', 'application/xml'); + + $this->assertEquals(['application/json', 'application/xml'], $request->getHeader('Content-Type')); + } + } + + public function testWithoutHeader() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedServerRequestFactory = new DecoratedServerRequestFactory($provider->getServerRequestFactory()); + + $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); + $request = $request + ->withHeader('Content-Length', 150) + ->withHeader('Content-Type', 'application/json'); + + $this->assertEquals(true, $request->hasHeader('Content-Length')); + + $request = $request->withoutHeader('Content-Length'); + $this->assertEquals(false, $request->hasHeader('Content-Length')); + } + } +} diff --git a/tests/Decorators/UriDecoratorTest.php b/tests/Decorators/UriDecoratorTest.php new file mode 100644 index 0000000..00fb59e --- /dev/null +++ b/tests/Decorators/UriDecoratorTest.php @@ -0,0 +1,448 @@ +factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri->foo = 'bar'; + + $this->assertFalse(property_exists($uri, 'foo')); + } + } + + public function testGetScheme() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $this->assertEquals('https', $uri->getScheme()); + } + } + + public function testWithScheme() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withScheme('http'); + + $this->assertEquals('http', $uri->getScheme()); + } + } + + public function testWithSchemeEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withScheme(''); + + $this->assertEquals('', $uri->getScheme()); + } + } + + public function testGetAuthorityWithUsernameAndPassword() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withUserInfo('user', 'password'); + + $this->assertEquals('user:password@google.com', $uri->getAuthority()); + } + } + + public function testGetAuthorityWithUsername() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://user@google.com'); + $uri = $uri->withUserInfo('user'); + + $this->assertEquals('user@google.com', $uri->getAuthority()); + } + } + + public function testGetAuthority() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://user@google.com'); + $this->assertEquals('user@google.com', $uri->getAuthority()); + } + } + + public function testGetAuthorityWithNonStandardPort() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com:400'); + $this->assertEquals('google.com:400', $uri->getAuthority()); + } + } + + public function testGetUserInfoWithUsernameAndPassword() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withUserInfo('user', 'password'); + + $this->assertEquals('user:password', $uri->getUserInfo()); + } + } + + public function testGetUserInfoWithUsernameAndPasswordEncodesCorrectly() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://user%40pass%3Aword@google.com'); + $this->assertEquals('user%40pass%3Aword', $uri->getUserInfo()); + } + } + + public function testGetUserInfoWithUsername() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://user@google.com'); + $this->assertEquals('user', $uri->getUserInfo()); + } + } + + public function testGetUserInfoNone() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $this->assertEquals('', $uri->getUserInfo()); + } + } + + public function testGetHost() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $this->assertEquals('google.com', $uri->getHost()); + } + } + + public function testWithHost() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withHost('microsoft.com'); + + $this->assertEquals('microsoft.com', $uri->getHost()); + } + } + + public function testWithPort() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withPort(400); + + $this->assertEquals(400, $uri->getPort()); + } + } + + public function testWithPortNull() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri = $uri->withPort(null); + + $this->assertEquals(null, $uri->getPort()); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWithPortInvalidInt() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri->withPort(70000); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWithPortInvalidString() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com'); + $uri->withPort('invalid'); + } + } + + public function testGetPath() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path'); + $this->assertEquals('/path', $uri->getPath()); + } + } + + public function testWithPath() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path'); + $uri = $uri->withPath('/newPath'); + + $this->assertEquals('/newPath', $uri->getPath()); + } + } + + public function testWithPathWithoutPrefix() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path'); + $uri = $uri->withPath('newPath'); + + $this->assertEquals('newPath', $uri->getPath()); + } + } + + public function testWithPathEmptyValue() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path'); + $uri = $uri->withPath(''); + + $this->assertEquals('', $uri->getPath()); + } + } + + public function testGetQuery() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path?foo=bar'); + $this->assertEquals('foo=bar', $uri->getQuery()); + } + } + + public function testWithQuery() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path?foo=bar'); + $uri = $uri->withQuery('bar=baz'); + + $this->assertEquals('bar=baz', $uri->getQuery()); + } + } + + public function testWithQueryEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path?foo=bar'); + $uri = $uri->withQuery(''); + + $this->assertEquals('', $uri->getQuery()); + } + } + + public function testGetFragment() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/#fragment'); + $this->assertEquals('fragment', $uri->getFragment()); + } + } + + public function testWithFragment() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/#fragment'); + $uri = $uri->withFragment('new-fragment'); + + $this->assertEquals('new-fragment', $uri->getFragment()); + } + } + + public function testWithFragmentEmpty() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/#fragment'); + $uri = $uri->withFragment(''); + + $this->assertEquals('', $uri->getFragment()); + } + } + + public function testToString() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://user@google.com/path/otherpath?foo=bar#fragment'); + $this->assertEquals('https://user@google.com/path/otherpath?foo=bar#fragment', (string) $uri); + + $uri = $uri->withPath('new-path'); + $this->assertEquals('https://user@google.com/new-path?foo=bar#fragment', (string) $uri); + + $uri = $uri->withPath('/other-path'); + $this->assertEquals('https://user@google.com/other-path?foo=bar#fragment', (string) $uri); + } + } + + public function testGetBaseUrl() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/path/otherpath?foo=bar#fragment'); + $this->assertEquals('https://google.com', $uri->getBaseUrl()); + } + } + + public function testGetBaseUrlWithNoBasePath() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://google.com/index.php'); + $this->assertEquals('https://google.com', $uri->getBaseUrl()); + } + } + + public function testGetBaseUrlWithAuthority() + { + foreach ($this->factoryProviders as $factoryProvider) { + /** @var Psr17FactoryProvider $provider */ + $provider = new $factoryProvider; + $decoratedUriFactory = new DecoratedUriFactory($provider->getUriFactory()); + + $uri = $decoratedUriFactory->createUri('https://user:password@google.com/path/otherpath?foo=bar#fragment'); + $this->assertEquals('https://user:password@google.com', $uri->getBaseUrl()); + } + } +} From 14753209eaf090a2375907ba8eee52a3cb17848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:23:09 -0600 Subject: [PATCH 09/29] downgrade PHP version to 7.0 in composer configuration --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 945f279..b8f36b8 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-json": "^1.6.0", "ext-libxml": "^7.2", "ext-SimpleXML": "^7.2", - "php": "^7.1", + "php": "^7.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", "php-http/message-factory": "^1.0" From 7cfbe60c44696e94d35838e015cdc9aaf0afccc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:32:49 -0600 Subject: [PATCH 10/29] upgrade php version to 7.1 to meet PHPUnit requirement --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index b8f36b8..307cd4a 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,9 @@ ], "require": { "ext-json": "^1.6.0", - "ext-libxml": "^7.2", - "ext-SimpleXML": "^7.2", - "php": "^7.0", + "ext-libxml": "^7.1", + "ext-SimpleXML": "^7.1", + "php": "^7.1", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", "php-http/message-factory": "^1.0" From da3bc9dc032714667d84f969a9313468aa51b03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:34:17 -0600 Subject: [PATCH 11/29] remove PHP 7.0 from Travis-CI config --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 01cfd22..a388b30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ sudo: false language: php php: - - 7.0 - 7.1 - 7.2 - nightly From 2aa22f86a2f561f03dfd4dbac7bdb0a0430d547b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:37:30 -0600 Subject: [PATCH 12/29] attempt to fix Travic-CI config --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a388b30..a8bc2d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,14 +12,14 @@ matrix: - php: nightly before_script: - - if [[ "$TRAVIS_PHP_VERSION" == '7.0' ]]; then composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n ; fi - - if [[ "$TRAVIS_PHP_VERSION" != '7.0' ]]; then composer install -n ; fi + - then composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n ; fi + - then composer install -n ; fi script: - - if [[ "$TRAVIS_PHP_VERSION" == '7.0' ]]; then mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml ; fi - - if [[ "$TRAVIS_PHP_VERSION" != '7.0' ]]; then vendor/bin/phpunit ; fi - - if [[ "$TRAVIS_PHP_VERSION" == '7.0' ]]; then vendor/bin/phpcs ; fi + - then mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml ; fi + - then vendor/bin/phpunit ; fi + - then vendor/bin/phpcs ; fi - vendor/bin/phpstan analyse -l 3 src/ after_script: - - if [[ "$TRAVIS_PHP_VERSION" == '7.0' ]]; then php vendor/bin/coveralls -v ; fi + - then php vendor/bin/coveralls -v ; fi From 20f75e4fc5935578fc46e3013f4d21a3ee730b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:39:20 -0600 Subject: [PATCH 13/29] attempt to fix Travis-CI config --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8bc2d3..8814fb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,14 +12,14 @@ matrix: - php: nightly before_script: - - then composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n ; fi - - then composer install -n ; fi + - composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n ; fi + - composer install -n ; fi script: - - then mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml ; fi - - then vendor/bin/phpunit ; fi - - then vendor/bin/phpcs ; fi + - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml ; fi + - vendor/bin/phpunit ; fi + - vendor/bin/phpcs ; fi - vendor/bin/phpstan analyse -l 3 src/ after_script: - - then php vendor/bin/coveralls -v ; fi + - php vendor/bin/coveralls -v ; fi From 6325430529077e419f6e8f133b1198eb1579cabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:41:34 -0600 Subject: [PATCH 14/29] attempt to fix Travis-CI config --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8814fb5..fccb9b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,14 +12,14 @@ matrix: - php: nightly before_script: - - composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n ; fi - - composer install -n ; fi + - composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n + - composer install -n script: - - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml ; fi - - vendor/bin/phpunit ; fi - - vendor/bin/phpcs ; fi + - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml + - vendor/bin/phpunit + - vendor/bin/phpcs - vendor/bin/phpstan analyse -l 3 src/ after_script: - - php vendor/bin/coveralls -v ; fi + - php vendor/bin/coveralls -v From f3ff3fc3e62dd99902d5df848508ce710498e550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:49:41 -0600 Subject: [PATCH 15/29] attempt to fix Travis-CI config --- .travis.yml | 2 +- composer.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index fccb9b9..50b38f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - php: nightly before_script: - - composer require satooshi/php-coveralls:^0.7 squizlabs/php_codesniffer:^2.5 -n + - composer require php-coveralls/php-coveralls:^2.1.0 squizlabs/php_codesniffer:^2.5 -n - composer install -n script: diff --git a/composer.json b/composer.json index 307cd4a..13ddb29 100644 --- a/composer.json +++ b/composer.json @@ -23,9 +23,9 @@ } ], "require": { - "ext-json": "^1.6.0", - "ext-libxml": "^7.1", - "ext-SimpleXML": "^7.1", + "ext-json": "*", + "ext-libxml": "*", + "ext-SimpleXML": "*", "php": "^7.1", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", From 4826201bcd31972c9720056c13e8573c992e57ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Tue, 16 Oct 2018 01:53:23 -0600 Subject: [PATCH 16/29] remove phpstan and upgrade codesniffer version in Travis-CI config --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 50b38f9..1636541 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,14 +12,13 @@ matrix: - php: nightly before_script: - - composer require php-coveralls/php-coveralls:^2.1.0 squizlabs/php_codesniffer:^2.5 -n + - composer require php-coveralls/php-coveralls:^2.1.0 squizlabs/php_codesniffer:^3.3.2 -n - composer install -n script: - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml - vendor/bin/phpunit - vendor/bin/phpcs - - vendor/bin/phpstan analyse -l 3 src/ after_script: - php vendor/bin/coveralls -v From 2a9bf6de464076c088b4bbb24dc6ab3cc4c2f718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=83=C2=A9rub=C3=83=C2=A9?= Date: Tue, 16 Oct 2018 02:08:21 -0600 Subject: [PATCH 17/29] update codesniffer version and add phpstan in composer.json and Travis-CI config --- .travis.yml | 3 ++- composer.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1636541..a0e41f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,14 @@ matrix: - php: nightly before_script: - - composer require php-coveralls/php-coveralls:^2.1.0 squizlabs/php_codesniffer:^3.3.2 -n + - composer require php-coveralls/php-coveralls:^2.1.0 - composer install -n script: - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml - vendor/bin/phpunit - vendor/bin/phpcs + - vendor/bin/phpstan analyse -l 3 src/ after_script: - php vendor/bin/coveralls -v diff --git a/composer.json b/composer.json index 13ddb29..5ad4449 100644 --- a/composer.json +++ b/composer.json @@ -33,10 +33,11 @@ }, "require-dev": { "http-interop/http-factory-tests": "dev-master", + "nyholm/psr7": "^1.0", "phpunit/phpunit": "^7.0", "php-http/psr7-integration-tests": "dev-master", - "nyholm/psr7": "^1.0", - "squizlabs/php_codesniffer": "^2.5", + "phpstan/phpstan": "^0.10.3", + "squizlabs/php_codesniffer": "^3.3.2", "zendframework/zend-diactoros": "^2.0" }, "provide": { From 5df6a0c56e9c8840ee5bb4ec10f389fc1a5beeba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=83=C2=A9rub=C3=83=C2=A9?= Date: Tue, 16 Oct 2018 02:08:21 -0600 Subject: [PATCH 18/29] fix phpstan errors --- src/Decorators/ResponseDecorator.php | 4 ++-- src/Decorators/ServerRequestDecorator.php | 4 ++-- src/Decorators/UriDecorator.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Decorators/ResponseDecorator.php b/src/Decorators/ResponseDecorator.php index 96a14fd..4648b7e 100644 --- a/src/Decorators/ResponseDecorator.php +++ b/src/Decorators/ResponseDecorator.php @@ -50,8 +50,8 @@ public function __construct(ResponseInterface $response, StreamFactoryInterface /** * Disable magic setter to ensure immutability - * @param $name - * @param $value + * @param mixed $name + * @param mixed $value */ public function __set($name, $value) { diff --git a/src/Decorators/ServerRequestDecorator.php b/src/Decorators/ServerRequestDecorator.php index f8b7309..2874315 100644 --- a/src/Decorators/ServerRequestDecorator.php +++ b/src/Decorators/ServerRequestDecorator.php @@ -81,8 +81,8 @@ public function __construct(ServerRequestInterface $serverRequest) /** * Disable magic setter to ensure immutability - * @param $name - * @param $value + * @param mixed $name + * @param mixed $value */ public function __set($name, $value) { diff --git a/src/Decorators/UriDecorator.php b/src/Decorators/UriDecorator.php index 59ef40b..c4ba9d4 100644 --- a/src/Decorators/UriDecorator.php +++ b/src/Decorators/UriDecorator.php @@ -33,8 +33,8 @@ public function __construct(UriInterface $uri) /** * Disable magic setter to ensure immutability - * @param $name - * @param $value + * @param mixed $name + * @param mixed $value */ public function __set($name, $value) { From df8d0370aac911934a6c875bb73a3a3d53bb9942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=83=C2=A9rub=C3=83=C2=A9?= Date: Tue, 16 Oct 2018 02:08:21 -0600 Subject: [PATCH 19/29] change minimum PHP version to 7.1 in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cf40bf..0ab7dcd 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ $ composer require slim/http "^0.1" ``` This will install the `slim/http` component and all required dependencies. -PHP 7.0, or newer, is required. +PHP 7.1, or newer, is required. ## Usage From 787b0ea0b2cb1bc65f5faaba3cc1b3b577816745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Wed, 17 Oct 2018 02:45:40 -0600 Subject: [PATCH 20/29] remove unused dependencies http-factory-tests and psr7-integration-tests --- composer.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/composer.json b/composer.json index 5ad4449..3b522ed 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,8 @@ "php-http/message-factory": "^1.0" }, "require-dev": { - "http-interop/http-factory-tests": "dev-master", "nyholm/psr7": "^1.0", "phpunit/phpunit": "^7.0", - "php-http/psr7-integration-tests": "dev-master", "phpstan/phpstan": "^0.10.3", "squizlabs/php_codesniffer": "^3.3.2", "zendframework/zend-diactoros": "^2.0" From d0551be48d15069c6b17824194fdf1970ddb9ab1 Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Mon, 22 Oct 2018 20:48:20 +0100 Subject: [PATCH 21/29] Integrate PHPStan to level 3 Use phpstan.neon.dist to ignore the tests that are expected to fail static analysis checks. Fix other PHPStan errors. --- composer.json | 6 ++++-- phpstan.neon.dist | 6 ++++++ tests/Decorators/ServerRequestDecoratorTest.php | 12 +++++++++--- tests/Test.php | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/composer.json b/composer.json index 3b522ed..0c92104 100644 --- a/composer.json +++ b/composer.json @@ -54,9 +54,11 @@ "scripts": { "test": [ "@phpunit", - "@phpcs" + "@phpcs", + "@phpstan" ], "phpunit": "php vendor/bin/phpunit", - "phpcs": "php vendor/bin/phpcs" + "phpcs": "php vendor/bin/phpcs", + "phpstan": "php -d memory_limit=-1 vendor/bin/phpstan analyse src tests" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..22a95ad --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,6 @@ +parameters: + level: 3 + ignoreErrors: + - '#Access to an undefined property Slim\\Http\\Decorators\\ResponseDecorator\:\:\$#' + - '#Access to an undefined property Slim\\Http\\Decorators\\ServerRequestDecorator\:\:\$#' + - '#Access to an undefined property Slim\\Http\\Decorators\\UriDecorator\:\:\$#' diff --git a/tests/Decorators/ServerRequestDecoratorTest.php b/tests/Decorators/ServerRequestDecoratorTest.php index c6bf00a..7bf562f 100644 --- a/tests/Decorators/ServerRequestDecoratorTest.php +++ b/tests/Decorators/ServerRequestDecoratorTest.php @@ -796,7 +796,9 @@ public function testGetParsedBodyXml() ->withHeader('Content-Type', 'application/hal+xml;charset=utf8') ->withBody($stream); - $this->assertEquals('John', $request->getParsedBody()->name); + /** @var \stdClass $obj */ + $obj = $request->getParsedBody(); + $this->assertEquals('John', $obj->name); } } @@ -815,7 +817,9 @@ public function testGetParsedBodyStructuredSuffixXml() ->withHeader('Content-Type', 'application/xml;charset=utf8') ->withBody($stream); - $this->assertEquals('John', $request->getParsedBody()->name); + /** @var \stdClass $obj */ + $obj = $request->getParsedBody(); + $this->assertEquals('John', $obj->name); } } @@ -834,7 +838,9 @@ public function testGetParsedBodyXmlWithTextXMLMediaType() ->withHeader('Content-Type', 'text/xml') ->withBody($stream); - $this->assertEquals('John', $request->getParsedBody()->name); + /** @var \stdClass $obj */ + $obj = $request->getParsedBody(); + $this->assertEquals('John', $obj->name); } } diff --git a/tests/Test.php b/tests/Test.php index 60ba628..e3c832b 100644 --- a/tests/Test.php +++ b/tests/Test.php @@ -20,7 +20,7 @@ abstract class Test extends TestCase { /** - * @var Psr17FactoryProvider[] + * @var string[] */ protected $factoryProviders = [ NyholmPsr17FactoryProvider::class, From 2020cfb8c88fe28be4647855196f5a0cb12892f7 Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Mon, 22 Oct 2018 21:09:59 +0100 Subject: [PATCH 22/29] Set PHPStan level to max and fix errors --- phpstan.neon.dist | 9 +++++---- src/Decorators/ResponseDecorator.php | 4 ++-- src/Decorators/ServerRequestDecorator.php | 16 ++++++++++++---- tests/Decorators/ServerRequestDecoratorTest.php | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 22a95ad..4d884ad 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,6 +1,7 @@ parameters: - level: 3 + level: max ignoreErrors: - - '#Access to an undefined property Slim\\Http\\Decorators\\ResponseDecorator\:\:\$#' - - '#Access to an undefined property Slim\\Http\\Decorators\\ServerRequestDecorator\:\:\$#' - - '#Access to an undefined property Slim\\Http\\Decorators\\UriDecorator\:\:\$#' + - '#Access to an undefined property Slim\\Http\\Decorators\\ResponseDecorator::\$foo#' + - '#Access to an undefined property Slim\\Http\\Decorators\\ServerRequestDecorator::\$foo#' + - '#Access to an undefined property Slim\\Http\\Decorators\\UriDecorator::\$foo#' + - '#Parameter \#1 \$port of method Slim\\Http\\Decorators\\UriDecorator::withPort() expects int|null, string given#' diff --git a/src/Decorators/ResponseDecorator.php b/src/Decorators/ResponseDecorator.php index 4648b7e..dbf1766 100644 --- a/src/Decorators/ResponseDecorator.php +++ b/src/Decorators/ResponseDecorator.php @@ -345,7 +345,7 @@ public function withJson($data, int $status = null, int $options = 0, int $depth { $json = json_encode($data, $options, $depth); - if (json_last_error() !== JSON_ERROR_NONE) { + if ($json === false || json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException(json_last_error_msg(), json_last_error()); } @@ -375,7 +375,7 @@ public function withJson($data, int $status = null, int $options = 0, int $depth */ public function withRedirect(string $url, $status = null): ResponseInterface { - $response = $this->withHeader('Location', (string) $url); + $response = $this->withHeader('Location', $url); if (!is_null($status)) { return $response->withStatus($status); diff --git a/src/Decorators/ServerRequestDecorator.php b/src/Decorators/ServerRequestDecorator.php index 2874315..afefba1 100644 --- a/src/Decorators/ServerRequestDecorator.php +++ b/src/Decorators/ServerRequestDecorator.php @@ -256,6 +256,9 @@ public function getParsedBody() } $mediaType = $this->getMediaType(); + if ($mediaType === null) { + return null; + } // look for a media type with a structured syntax suffix (RFC 6839) $parts = explode('+', $mediaType); @@ -837,6 +840,9 @@ public function getMediaType() $contentType = $this->getContentType(); if ($contentType) { $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + if ($contentTypeParts === false) { + return null; + } return strtolower($contentTypeParts[0]); } @@ -856,10 +862,12 @@ public function getMediaTypeParams() $contentTypeParams = []; if ($contentType) { $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); - $contentTypePartsLength = count($contentTypeParts); - for ($i = 1; $i < $contentTypePartsLength; $i++) { - $paramParts = explode('=', $contentTypeParts[$i]); - $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; + if ($contentTypeParts !== false) { + $contentTypePartsLength = count($contentTypeParts); + for ($i = 1; $i < $contentTypePartsLength; $i++) { + $paramParts = explode('=', $contentTypeParts[$i]); + $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; + } } } diff --git a/tests/Decorators/ServerRequestDecoratorTest.php b/tests/Decorators/ServerRequestDecoratorTest.php index 7bf562f..6b2de83 100644 --- a/tests/Decorators/ServerRequestDecoratorTest.php +++ b/tests/Decorators/ServerRequestDecoratorTest.php @@ -1150,7 +1150,7 @@ public function testWithoutHeader() $request = $decoratedServerRequestFactory->createServerRequest('GET', 'https://google.com'); $request = $request - ->withHeader('Content-Length', 150) + ->withHeader('Content-Length', '150') ->withHeader('Content-Type', 'application/json'); $this->assertEquals(true, $request->hasHeader('Content-Length')); From 2244484b9afdc0665047baa0a8d022e9298bebbc Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Mon, 22 Oct 2018 21:35:23 +0100 Subject: [PATCH 23/29] Cast the output of json_encode() to a string when creating a stream This allows encoding of false. --- src/Decorators/ResponseDecorator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Decorators/ResponseDecorator.php b/src/Decorators/ResponseDecorator.php index dbf1766..81b10bd 100644 --- a/src/Decorators/ResponseDecorator.php +++ b/src/Decorators/ResponseDecorator.php @@ -345,13 +345,13 @@ public function withJson($data, int $status = null, int $options = 0, int $depth { $json = json_encode($data, $options, $depth); - if ($json === false || json_last_error() !== JSON_ERROR_NONE) { + if (json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException(json_last_error_msg(), json_last_error()); } $response = $this ->withHeader('Content-Type', 'application/json;charset=utf-8') - ->withBody($this->streamFactory->createStream($json)); + ->withBody($this->streamFactory->createStream((string)$json)); if ($status !== null) { $response = $response->withStatus($status); From f40a2eb76042776de5d7b3ed3d0b3ee3c618b3bc Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Mon, 22 Oct 2018 22:13:49 +0100 Subject: [PATCH 24/29] Test against psr7-integration-tests Belt and braces tests as our tests should cover everything here anyway. --- composer.json | 5 +-- tests/Psr7Integration/Nyholm/ResponseTest.php | 34 +++++++++++++++++++ .../Nyholm/ServerRequestTest.php | 32 +++++++++++++++++ tests/Psr7Integration/Nyholm/UriTest.php | 32 +++++++++++++++++ tests/Psr7Integration/Zend/ResponseTest.php | 34 +++++++++++++++++++ .../Zend/ServerRequestTest.php | 31 +++++++++++++++++ tests/Psr7Integration/Zend/UriTest.php | 31 +++++++++++++++++ 7 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 tests/Psr7Integration/Nyholm/ResponseTest.php create mode 100644 tests/Psr7Integration/Nyholm/ServerRequestTest.php create mode 100644 tests/Psr7Integration/Nyholm/UriTest.php create mode 100644 tests/Psr7Integration/Zend/ResponseTest.php create mode 100644 tests/Psr7Integration/Zend/ServerRequestTest.php create mode 100644 tests/Psr7Integration/Zend/UriTest.php diff --git a/composer.json b/composer.json index 0c92104..0494d8c 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ "phpunit/phpunit": "^7.0", "phpstan/phpstan": "^0.10.3", "squizlabs/php_codesniffer": "^3.3.2", - "zendframework/zend-diactoros": "^2.0" + "zendframework/zend-diactoros": "^2.0", + "php-http/psr7-integration-tests": "dev-master" }, "provide": { "php-http/message-factory": "^1.0" @@ -57,7 +58,7 @@ "@phpcs", "@phpstan" ], - "phpunit": "php vendor/bin/phpunit", + "phpunit": "php vendor/bin/phpunit --process-isolation", "phpcs": "php vendor/bin/phpcs", "phpstan": "php -d memory_limit=-1 vendor/bin/phpstan analyse src tests" } diff --git a/tests/Psr7Integration/Nyholm/ResponseTest.php b/tests/Psr7Integration/Nyholm/ResponseTest.php new file mode 100644 index 0000000..6dc9665 --- /dev/null +++ b/tests/Psr7Integration/Nyholm/ResponseTest.php @@ -0,0 +1,34 @@ +getResponseFactory(), + $provider->getStreamFactory() + ); + + return $decoratedResponseFactory->createResponse(); + } +} diff --git a/tests/Psr7Integration/Nyholm/ServerRequestTest.php b/tests/Psr7Integration/Nyholm/ServerRequestTest.php new file mode 100644 index 0000000..fbeb767 --- /dev/null +++ b/tests/Psr7Integration/Nyholm/ServerRequestTest.php @@ -0,0 +1,32 @@ +getServerRequestFactory()); + + return $decoratedServerRequestFactory->createServerRequest('GET', 'http://foo.com', $_SERVER); + } +} diff --git a/tests/Psr7Integration/Nyholm/UriTest.php b/tests/Psr7Integration/Nyholm/UriTest.php new file mode 100644 index 0000000..d98cffb --- /dev/null +++ b/tests/Psr7Integration/Nyholm/UriTest.php @@ -0,0 +1,32 @@ +getUriFactory()); + + return $decoratedUriFactory->createUri($uri); + } +} diff --git a/tests/Psr7Integration/Zend/ResponseTest.php b/tests/Psr7Integration/Zend/ResponseTest.php new file mode 100644 index 0000000..265446d --- /dev/null +++ b/tests/Psr7Integration/Zend/ResponseTest.php @@ -0,0 +1,34 @@ +getResponseFactory(), + $provider->getStreamFactory() + ); + + return $decoratedResponseFactory->createResponse(); + } +} diff --git a/tests/Psr7Integration/Zend/ServerRequestTest.php b/tests/Psr7Integration/Zend/ServerRequestTest.php new file mode 100644 index 0000000..0027529 --- /dev/null +++ b/tests/Psr7Integration/Zend/ServerRequestTest.php @@ -0,0 +1,31 @@ +getServerRequestFactory()); + + return $decoratedServerRequestFactory->createServerRequest('GET', 'http://foo.com', $_SERVER); + } +} diff --git a/tests/Psr7Integration/Zend/UriTest.php b/tests/Psr7Integration/Zend/UriTest.php new file mode 100644 index 0000000..f5fa18b --- /dev/null +++ b/tests/Psr7Integration/Zend/UriTest.php @@ -0,0 +1,31 @@ +getUriFactory()); + + return $decoratedUriFactory->createUri($uri); + } +} From 97c68f8280a6f730fa2f934e265722c5b386d48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Mon, 22 Oct 2018 18:12:23 -0600 Subject: [PATCH 25/29] refactor to use underlying implementation instead of cloning in all withX and isX methods --- src/Decorators/ResponseDecorator.php | 65 ++++++++---------- src/Decorators/ServerRequestDecorator.php | 83 +++++++++++------------ src/Decorators/UriDecorator.php | 25 +++---- 3 files changed, 79 insertions(+), 94 deletions(-) diff --git a/src/Decorators/ResponseDecorator.php b/src/Decorators/ResponseDecorator.php index 81b10bd..3a8335d 100644 --- a/src/Decorators/ResponseDecorator.php +++ b/src/Decorators/ResponseDecorator.php @@ -215,8 +215,7 @@ public function hasHeader($name) public function withAddedHeader($name, $value) { $response = $this->response->withAddedHeader($name, $value); - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -235,8 +234,7 @@ public function withAddedHeader($name, $value) public function withBody(StreamInterface $body) { $response = $this->response->withBody($body); - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -257,8 +255,7 @@ public function withBody(StreamInterface $body) public function withHeader($name, $value) { $response = $this->response->withHeader($name, $value); - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -276,8 +273,7 @@ public function withHeader($name, $value) public function withoutHeader($name) { $response = $this->response->withoutHeader($name); - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -296,8 +292,7 @@ public function withoutHeader($name) public function withProtocolVersion($version) { $response = $this->response->withProtocolVersion($version); - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -323,8 +318,7 @@ public function withProtocolVersion($version) public function withStatus($code, $reasonPhrase = '') { $response = $this->response->withStatus($code, $reasonPhrase); - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -343,22 +337,21 @@ public function withStatus($code, $reasonPhrase = '') */ public function withJson($data, int $status = null, int $options = 0, int $depth = 512): ResponseInterface { - $json = json_encode($data, $options, $depth); + $json = (string) json_encode($data, $options, $depth); if (json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException(json_last_error_msg(), json_last_error()); } - $response = $this + $response = $this->response ->withHeader('Content-Type', 'application/json;charset=utf-8') - ->withBody($this->streamFactory->createStream((string)$json)); + ->withBody($this->streamFactory->createStream($json)); if ($status !== null) { $response = $response->withStatus($status); } - $clone = clone $response; - return new ResponseDecorator($clone, $this->streamFactory); + return new ResponseDecorator($response, $this->streamFactory); } /** @@ -371,11 +364,11 @@ public function withJson($data, int $status = null, int $options = 0, int $depth * * @param string $url The redirect destination. * @param int|null $status The redirect HTTP status code. - * @return ResponseDecorator + * @return ResponseInterface */ public function withRedirect(string $url, $status = null): ResponseInterface { - $response = $this->withHeader('Location', $url); + $response = $this->response->withHeader('Location', $url); if (!is_null($status)) { return $response->withStatus($status); @@ -396,7 +389,7 @@ public function withRedirect(string $url, $status = null): ResponseInterface */ public function write($data) { - $this->getBody()->write($data); + $this->response->getBody()->write($data); return $this; } @@ -409,7 +402,7 @@ public function write($data) */ public function isClientError(): bool { - return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; + return $this->response->getStatusCode() >= 400 && $this->response->getStatusCode() < 500; } /** @@ -421,7 +414,7 @@ public function isClientError(): bool */ public function isEmpty(): bool { - return in_array($this->getStatusCode(), [204, 205, 304]); + return in_array($this->response->getStatusCode(), [204, 205, 304]); } /** @@ -434,7 +427,7 @@ public function isEmpty(): bool */ public function isForbidden(): bool { - return $this->getStatusCode() === 403; + return $this->response->getStatusCode() === 403; } /** @@ -446,7 +439,7 @@ public function isForbidden(): bool */ public function isInformational(): bool { - return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; + return $this->response->getStatusCode() >= 100 && $this->response->getStatusCode() < 200; } /** @@ -458,7 +451,7 @@ public function isInformational(): bool */ public function isOk(): bool { - return $this->getStatusCode() === 200; + return $this->response->getStatusCode() === 200; } /** @@ -470,7 +463,7 @@ public function isOk(): bool */ public function isNotFound(): bool { - return $this->getStatusCode() === 404; + return $this->response->getStatusCode() === 404; } /** @@ -482,7 +475,7 @@ public function isNotFound(): bool */ public function isRedirect(): bool { - return in_array($this->getStatusCode(), [301, 302, 303, 307, 308]); + return in_array($this->response->getStatusCode(), [301, 302, 303, 307, 308]); } /** @@ -494,7 +487,7 @@ public function isRedirect(): bool */ public function isRedirection(): bool { - return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; + return $this->response->getStatusCode() >= 300 && $this->response->getStatusCode() < 400; } /** @@ -506,7 +499,7 @@ public function isRedirection(): bool */ public function isServerError(): bool { - return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; + return $this->response->getStatusCode() >= 500 && $this->response->getStatusCode() < 600; } /** @@ -518,7 +511,7 @@ public function isServerError(): bool */ public function isSuccessful(): bool { - return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; + return $this->response->getStatusCode() >= 200 && $this->response->getStatusCode() < 300; } /** @@ -532,18 +525,18 @@ public function __toString(): string { $output = sprintf( 'HTTP/%s %s %s%s', - $this->getProtocolVersion(), - $this->getStatusCode(), - $this->getReasonPhrase(), + $this->response->getProtocolVersion(), + $this->response->getStatusCode(), + $this->response->getReasonPhrase(), self::EOL ); - foreach ($this->getHeaders() as $name => $values) { - $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)) . self::EOL; + foreach ($this->response->getHeaders() as $name => $values) { + $output .= sprintf('%s: %s', $name, $this->response->getHeaderLine($name)) . self::EOL; } $output .= self::EOL; - $output .= (string) $this->getBody(); + $output .= (string) $this->response->getBody(); return $output; } diff --git a/src/Decorators/ServerRequestDecorator.php b/src/Decorators/ServerRequestDecorator.php index afefba1..92de46d 100644 --- a/src/Decorators/ServerRequestDecorator.php +++ b/src/Decorators/ServerRequestDecorator.php @@ -41,9 +41,11 @@ public function __construct(ServerRequestInterface $serverRequest) $this->registerMediaTypeParser('application/json', function ($input) { $result = json_decode($input, true); + if (!is_array($result)) { return null; } + return $result; }); @@ -51,12 +53,15 @@ public function __construct(ServerRequestInterface $serverRequest) $backup = libxml_disable_entity_loader(true); $backup_errors = libxml_use_internal_errors(true); $result = simplexml_load_string($input); + libxml_disable_entity_loader($backup); libxml_clear_errors(); libxml_use_internal_errors($backup_errors); + if ($result === false) { return null; } + return $result; }); @@ -64,12 +69,15 @@ public function __construct(ServerRequestInterface $serverRequest) $backup = libxml_disable_entity_loader(true); $backup_errors = libxml_use_internal_errors(true); $result = simplexml_load_string($input); + libxml_disable_entity_loader($backup); libxml_clear_errors(); libxml_use_internal_errors($backup_errors); + if ($result === false) { return null; } + return $result; }); @@ -260,7 +268,7 @@ public function getParsedBody() return null; } - // look for a media type with a structured syntax suffix (RFC 6839) + // Look for a media type with a structured syntax suffix (RFC 6839) $parts = explode('+', $mediaType); if (count($parts) >= 2) { $mediaType = 'application/' . $parts[count($parts)-1]; @@ -418,8 +426,7 @@ public function hasHeader($name) public function withAddedHeader($name, $value) { $serverRequest = $this->serverRequest->withAddedHeader($name, $value); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -440,8 +447,7 @@ public function withAddedHeader($name, $value) public function withAttribute($name, $value) { $serverRequest = $this->serverRequest->withAttribute($name, $value); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -457,18 +463,17 @@ public function withAttribute($name, $value) * updated attributes. * * @param array $attributes New attributes - * @return static + * @return ServerRequestDecorator */ public function withAttributes(array $attributes) { $serverRequest = $this->serverRequest; - $clone = clone $serverRequest; foreach ($attributes as $attribute => $value) { - $clone = $clone->withAttribute($attribute, $value); + $serverRequest = $serverRequest->withAttribute($attribute, $value); } - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -488,8 +493,7 @@ public function withAttributes(array $attributes) public function withoutAttribute($name) { $serverRequest = $this->serverRequest->withoutAttribute($name); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -508,8 +512,7 @@ public function withoutAttribute($name) public function withBody(StreamInterface $body) { $serverRequest = $this->serverRequest->withBody($body); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -532,8 +535,7 @@ public function withBody(StreamInterface $body) public function withCookieParams(array $cookies) { $serverRequest = $this->serverRequest->withCookieParams($cookies); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -554,8 +556,7 @@ public function withCookieParams(array $cookies) public function withHeader($name, $value) { $serverRequest = $this->serverRequest->withHeader($name, $value); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -573,8 +574,7 @@ public function withHeader($name, $value) public function withoutHeader($name) { $serverRequest = $this->serverRequest->withoutHeader($name); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -595,8 +595,7 @@ public function withoutHeader($name) public function withMethod($method) { $serverRequest = $this->serverRequest->withMethod($method); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -630,8 +629,7 @@ public function withMethod($method) public function withParsedBody($data) { $serverRequest = $this->serverRequest->withParsedBody($data); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -650,8 +648,7 @@ public function withParsedBody($data) public function withProtocolVersion($version) { $serverRequest = $this->serverRequest->withProtocolVersion($version); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -679,8 +676,7 @@ public function withProtocolVersion($version) public function withQueryParams(array $query) { $serverRequest = $this->serverRequest->withQueryParams($query); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -703,8 +699,7 @@ public function withQueryParams(array $query) public function withRequestTarget($requestTarget) { $serverRequest = $this->serverRequest->withRequestTarget($requestTarget); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -721,8 +716,7 @@ public function withRequestTarget($requestTarget) public function withUploadedFiles(array $uploadedFiles) { $serverRequest = $this->serverRequest->withUploadedFiles($uploadedFiles); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -758,8 +752,7 @@ public function withUploadedFiles(array $uploadedFiles) public function withUri(UriInterface $uri, $preserveHost = false) { $serverRequest = $this->serverRequest->withUri($uri, $preserveHost); - $clone = clone $serverRequest; - return new ServerRequestDecorator($clone); + return new ServerRequestDecorator($serverRequest); } /** @@ -772,6 +765,7 @@ public function withUri(UriInterface $uri, $preserveHost = false) public function getContentCharset() { $mediaTypeParams = $this->getMediaTypeParams(); + if (isset($mediaTypeParams['charset'])) { return $mediaTypeParams['charset']; } @@ -788,8 +782,7 @@ public function getContentCharset() */ public function getContentType() { - $result = $this->getHeader('Content-Type'); - + $result = $this->serverRequest->getHeader('Content-Type'); return $result ? $result[0] : null; } @@ -802,8 +795,7 @@ public function getContentType() */ public function getContentLength() { - $result = $this->getHeader('Content-Length'); - + $result = $this->serverRequest->getHeader('Content-Length'); return $result ? (int) $result[0] : null; } @@ -819,8 +811,9 @@ public function getContentLength() */ public function getCookieParam($key, $default = null) { - $cookies = $this->getCookieParams(); + $cookies = $this->serverRequest->getCookieParams(); $result = $default; + if (isset($cookies[$key])) { $result = $cookies[$key]; } @@ -838,6 +831,7 @@ public function getCookieParam($key, $default = null) public function getMediaType() { $contentType = $this->getContentType(); + if ($contentType) { $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); if ($contentTypeParts === false) { @@ -860,6 +854,7 @@ public function getMediaTypeParams() { $contentType = $this->getContentType(); $contentTypeParams = []; + if ($contentType) { $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); if ($contentTypeParts !== false) { @@ -889,6 +884,7 @@ public function getParam($key, $default = null) $postParams = $this->getParsedBody(); $getParams = $this->getQueryParams(); $result = $default; + if (is_array($postParams) && isset($postParams[$key])) { $result = $postParams[$key]; } elseif (is_object($postParams) && property_exists($postParams, $key)) { @@ -911,6 +907,7 @@ public function getParams() { $params = $this->getQueryParams(); $postParams = $this->getParsedBody(); + if ($postParams) { $params = array_merge($params, (array)$postParams); } @@ -932,6 +929,7 @@ public function getParsedBodyParam($key, $default = null) { $postParams = $this->getParsedBody(); $result = $default; + if (is_array($postParams) && isset($postParams[$key])) { $result = $postParams[$key]; } elseif (is_object($postParams) && property_exists($postParams, $key)) { @@ -955,6 +953,7 @@ public function getQueryParam($key, $default = null) { $getParams = $this->getQueryParams(); $result = $default; + if (isset($getParams[$key])) { $result = $getParams[$key]; } @@ -973,8 +972,7 @@ public function getQueryParam($key, $default = null) */ public function getServerParam($key, $default = null) { - $serverParams = $this->getServerParams(); - + $serverParams = $this->serverRequest->getServerParams(); return isset($serverParams[$key]) ? $serverParams[$key] : $default; } @@ -992,6 +990,7 @@ public function registerMediaTypeParser($mediaType, callable $callable) if ($callable instanceof Closure) { $callable = $callable->bindTo($this); } + $this->bodyParsers[$mediaType] = $callable; return $this; @@ -1043,7 +1042,7 @@ public function isHead() */ public function isMethod($method) { - return $this->getMethod() === $method; + return $this->serverRequest->getMethod() === $method; } /** @@ -1103,6 +1102,6 @@ public function isPut() */ public function isXhr() { - return $this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; + return $this->serverRequest->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; } } diff --git a/src/Decorators/UriDecorator.php b/src/Decorators/UriDecorator.php index c4ba9d4..8c080ce 100644 --- a/src/Decorators/UriDecorator.php +++ b/src/Decorators/UriDecorator.php @@ -231,8 +231,7 @@ public function getUserInfo() public function withFragment($fragment) { $uri = $this->uri->withFragment($fragment); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -250,8 +249,7 @@ public function withFragment($fragment) public function withHost($host) { $uri = $this->uri->withHost($host); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -279,8 +277,7 @@ public function withHost($host) public function withPath($path) { $uri = $this->uri->withPath($path); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -303,8 +300,7 @@ public function withPath($path) public function withPort($port) { $uri = $this->uri->withPort($port); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -325,8 +321,7 @@ public function withPort($port) public function withQuery($query) { $uri = $this->uri->withQuery($query); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -347,8 +342,7 @@ public function withQuery($query) public function withScheme($scheme) { $uri = $this->uri->withScheme($scheme); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -368,8 +362,7 @@ public function withScheme($scheme) public function withUserInfo($user, $password = null) { $uri = $this->uri->withUserInfo($user, $password); - $clone = clone $uri; - return new UriDecorator($clone); + return new UriDecorator($uri); } /** @@ -411,8 +404,8 @@ public function __toString() */ public function getBaseUrl() { - $scheme = $this->getScheme(); - $authority = $this->getAuthority(); + $scheme = $this->uri->getScheme(); + $authority = $this->uri->getAuthority(); return ($scheme !== '' ? $scheme . ':' : '') . ($authority !== '' ? '//' . $authority : ''); } } From 7db1e0124743ac3ff9e92d7ebe296e3c470a7fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Mon, 22 Oct 2018 18:22:02 -0600 Subject: [PATCH 26/29] fix Travis-CI phpstan function call --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a0e41f5..fe2613f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ script: - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml - vendor/bin/phpunit - vendor/bin/phpcs - - vendor/bin/phpstan analyse -l 3 src/ + - php -d memory_limit=-1 vendor/bin/phpstan analyse src tests after_script: - php vendor/bin/coveralls -v From 6955948ba1c7551dde406dfaa1c173d9c73686c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Mon, 22 Oct 2018 20:00:15 -0600 Subject: [PATCH 27/29] remove unused import in base Test class --- tests/Test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Test.php b/tests/Test.php index e3c832b..4c0f79f 100644 --- a/tests/Test.php +++ b/tests/Test.php @@ -9,7 +9,6 @@ namespace Slim\Tests\Http; use Slim\Tests\Http\Providers\NyholmPsr17FactoryProvider; -use Slim\Tests\Http\Providers\Psr17FactoryProvider; use Slim\Tests\Http\Providers\ZendDiactorosPsr17FactoryProvider; use PHPUnit\Framework\TestCase; From f7feaa3649b857bfa341a63da70a4e5bd2a11ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Mon, 22 Oct 2018 20:02:15 -0600 Subject: [PATCH 28/29] remove unused imports in Psr7Integration/Nyholm tests --- tests/Psr7Integration/Nyholm/ServerRequestTest.php | 1 - tests/Psr7Integration/Nyholm/UriTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/Psr7Integration/Nyholm/ServerRequestTest.php b/tests/Psr7Integration/Nyholm/ServerRequestTest.php index fbeb767..9a2089d 100644 --- a/tests/Psr7Integration/Nyholm/ServerRequestTest.php +++ b/tests/Psr7Integration/Nyholm/ServerRequestTest.php @@ -12,7 +12,6 @@ use Nyholm\Psr7\Factory\Psr17Factory; use Slim\Http\Factory\DecoratedServerRequestFactory; use Slim\Tests\Http\Providers\NyholmPsr17FactoryProvider; -use Slim\Tests\Http\Providers\Psr17FactoryProvider; class ServerRequestTest extends ServerRequestIntegrationTest { diff --git a/tests/Psr7Integration/Nyholm/UriTest.php b/tests/Psr7Integration/Nyholm/UriTest.php index d98cffb..b0a4cc2 100644 --- a/tests/Psr7Integration/Nyholm/UriTest.php +++ b/tests/Psr7Integration/Nyholm/UriTest.php @@ -12,7 +12,6 @@ use Nyholm\Psr7\Factory\Psr17Factory; use Slim\Http\Factory\DecoratedUriFactory; use Slim\Tests\Http\Providers\NyholmPsr17FactoryProvider; -use Slim\Tests\Http\Providers\Psr17FactoryProvider; class UriTest extends UriIntegrationTest { From 336f97c0516a333bfe82a0895c08794a95680d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=A9rub=C3=A9?= Date: Mon, 22 Oct 2018 20:04:03 -0600 Subject: [PATCH 29/29] remove memory limit setting used for phpstan in Travis-CI config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fe2613f..a3cc40a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ script: - mkdir -p build/logs && phpunit --coverage-clover build/logs/clover.xml - vendor/bin/phpunit - vendor/bin/phpcs - - php -d memory_limit=-1 vendor/bin/phpstan analyse src tests + - vendor/bin/phpstan analyse src tests after_script: - php vendor/bin/coveralls -v