From cf576e02cbf51c51767f62afe532999d47a7725f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Sat, 16 Feb 2019 21:33:20 +0100 Subject: [PATCH 1/9] update licence --- LICENSE | 62 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/LICENSE b/LICENSE index 09407b4..fc0aff4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,43 @@ -MIT License - -Copyright (c) 2017 Rancoud - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +MIT License + +Copyright (c) 2016 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +MIT License + +Copyright (c) 2017 Rancoud + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From 7d3aee16abc5798bf83d1dc0c26c9208a3fe4c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Mon, 25 Feb 2019 01:09:07 +0100 Subject: [PATCH 2/9] update --- LICENSE | 84 +++++ src/Message/Factory/Factory.php | 10 +- src/Message/MessageTrait.php | 6 +- src/Message/Request.php | 11 +- src/Message/RequestTrait.php | 6 +- src/Message/Response.php | 10 +- src/Message/ServerRequest.php | 6 +- src/Message/Stream.php | 12 +- src/Message/UploadedFile.php | 48 +-- src/Message/Uri.php | 16 +- src/Psr/Client/ClientException.php | 7 - src/Psr/Client/ClientExceptionInterface.php | 10 + src/Psr/Client/ClientInterface.php | 11 +- src/Psr/Client/Exception/NetworkException.php | 11 - src/Psr/Client/Exception/RequestException.php | 11 - src/Psr/Client/NetworkExceptionInterface.php | 24 ++ src/Psr/Client/RequestExceptionInterface.php | 24 ++ src/Psr/Message/MessageInterface.php | 171 +++++++++- src/Psr/Message/RequestFactoryInterface.php | 12 +- src/Psr/Message/RequestInterface.php | 118 ++++++- src/Psr/Message/ResponseFactoryInterface.php | 12 +- src/Psr/Message/ResponseInterface.php | 60 +++- .../Message/ServerRequestFactoryInterface.php | 18 +- src/Psr/Message/ServerRequestInterface.php | 243 +++++++++++++- src/Psr/Message/StreamFactoryInterface.php | 35 +- src/Psr/Message/StreamInterface.php | 138 +++++++- .../Message/UploadedFileFactoryInterface.php | 22 +- src/Psr/Message/UploadedFileInterface.php | 112 ++++++- src/Psr/Message/UriFactoryInterface.php | 13 +- src/Psr/Message/UriInterface.php | 304 +++++++++++++++++- src/Psr/Server/MiddlewareInterface.php | 21 +- src/Psr/Server/RequestHandlerInterface.php | 17 +- 32 files changed, 1494 insertions(+), 109 deletions(-) delete mode 100644 src/Psr/Client/ClientException.php create mode 100644 src/Psr/Client/ClientExceptionInterface.php delete mode 100644 src/Psr/Client/Exception/NetworkException.php delete mode 100644 src/Psr/Client/Exception/RequestException.php create mode 100644 src/Psr/Client/NetworkExceptionInterface.php create mode 100644 src/Psr/Client/RequestExceptionInterface.php diff --git a/LICENSE b/LICENSE index fc0aff4..3423f5d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,87 @@ +Copyright (c) 2014 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +MIT License + +Copyright (c) 2018 PHP-FIG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Copyright (c) 2017 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + MIT License Copyright (c) 2016 Tobias Nyholm diff --git a/src/Message/Factory/Factory.php b/src/Message/Factory/Factory.php index 75919a6..300206d 100644 --- a/src/Message/Factory/Factory.php +++ b/src/Message/Factory/Factory.php @@ -259,7 +259,7 @@ public function createServerRequestFromArrays( ->withCookieParams($cookie) ->withQueryParams($get) ->withParsedBody($post) - ->withUploadedFiles(self::normalizeFiles($files)); + ->withUploadedFiles(static::normalizeFiles($files)); } /** @@ -333,9 +333,9 @@ protected static function normalizeFiles(array $files): array if ($value instanceof UploadedFileInterface) { $normalized[$key] = $value; } elseif (\is_array($value) && isset($value['tmp_name'])) { - $normalized[$key] = self::createUploadedFileFromSpec($value); + $normalized[$key] = static::createUploadedFileFromSpec($value); } elseif (\is_array($value)) { - $normalized[$key] = self::normalizeFiles($value); + $normalized[$key] = static::normalizeFiles($value); continue; } else { @@ -356,7 +356,7 @@ protected static function normalizeFiles(array $files): array protected static function createUploadedFileFromSpec(array $value) { if (\is_array($value['tmp_name'])) { - return self::normalizeNestedFileSpec($value); + return static::normalizeNestedFileSpec($value); } return new UploadedFile( @@ -387,7 +387,7 @@ protected static function normalizeNestedFileSpec(array $files = []): array 'name' => $files['name'][$key], 'type' => $files['type'][$key], ]; - $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + $normalizedFiles[$key] = static::createUploadedFileFromSpec($spec); } return $normalizedFiles; diff --git a/src/Message/MessageTrait.php b/src/Message/MessageTrait.php index bdb8389..3e72465 100644 --- a/src/Message/MessageTrait.php +++ b/src/Message/MessageTrait.php @@ -259,12 +259,12 @@ protected function setHeaders(array $headers): void */ protected function validateAndTrimHeader($header, $values): array { - if (!\is_string($header) || \preg_match(self::$patternHeaderName, $header) !== 1) { + if (!\is_string($header) || \preg_match(static::$patternHeaderName, $header) !== 1) { throw new InvalidArgumentException('Header name must be RFC 7230 compatible string.'); } if (!\is_array($values)) { - if ((!\is_numeric($values) && !\is_string($values)) || \preg_match(self::$patternHeaderValue, (string) $values) !== 1) { + if ((!\is_numeric($values) && !\is_string($values)) || \preg_match(static::$patternHeaderValue, (string) $values) !== 1) { throw new InvalidArgumentException('Header value must be RFC 7230 compatible string.'); } @@ -277,7 +277,7 @@ protected function validateAndTrimHeader($header, $values): array $returnValues = []; foreach ($values as $v) { - if ((!\is_numeric($v) && !\is_string($v)) || \preg_match(self::$patternHeaderValue, (string) $v) !== 1) { + if ((!\is_numeric($v) && !\is_string($v)) || \preg_match(static::$patternHeaderValue, (string) $v) !== 1) { throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); } diff --git a/src/Message/Request.php b/src/Message/Request.php index bb815bf..373c683 100644 --- a/src/Message/Request.php +++ b/src/Message/Request.php @@ -5,6 +5,7 @@ namespace Rancoud\Http\Message; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; /** @@ -18,11 +19,11 @@ class Request implements RequestInterface /** * Request constructor. * - * @param string $method - * @param mixed $uri - * @param array $headers - * @param mixed $body - * @param string $version + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version * * @throws \InvalidArgumentException */ diff --git a/src/Message/RequestTrait.php b/src/Message/RequestTrait.php index d6807d8..4152299 100644 --- a/src/Message/RequestTrait.php +++ b/src/Message/RequestTrait.php @@ -70,10 +70,10 @@ trait RequestTrait /** @var string */ protected $method; - /** @var null|string */ + /** @var string|null */ protected $requestTarget; - /** @var null|UriInterface */ + /** @var UriInterface|null */ protected $uri; /** @@ -187,7 +187,7 @@ protected function filterMethod($method): void throw new InvalidArgumentException('Method must be a string'); } - if (!\in_array($method, self::$methods, true)) { + if (!\in_array($method, static::$methods, true)) { throw new InvalidArgumentException(\sprintf('Method %s is invalid', $method)); } } diff --git a/src/Message/Response.php b/src/Message/Response.php index f99e931..4f8720a 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -15,7 +15,7 @@ class Response implements ResponseInterface use MessageTrait; /** @var array */ - public static $phrases = [ + public const PHRASES = [ 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', @@ -206,8 +206,8 @@ public function __construct( } $this->setHeaders($headers); - if (($reason === null || $reason === '') && isset(self::$phrases[$this->statusCode])) { - $this->reasonPhrase = self::$phrases[$status]; + if (($reason === null || $reason === '') && isset(static::PHRASES[$this->statusCode])) { + $this->reasonPhrase = static::PHRASES[$status]; } else { $this->reasonPhrase = $reason; } @@ -238,14 +238,14 @@ public function withStatus($code, $reasonPhrase = ''): self } $code = (int) $code; - if (!isset(self::$phrases[$code])) { + if (!isset(static::PHRASES[$code])) { throw new InvalidArgumentException('Status code has to be an integer between 100 and 599'); } $new = clone $this; $new->statusCode = $code; if (($reasonPhrase === null || $reasonPhrase === '')) { - $reasonPhrase = self::$phrases[$new->statusCode]; + $reasonPhrase = static::PHRASES[$new->statusCode]; } $new->reasonPhrase = $reasonPhrase; diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php index 0db5ea5..caec5f9 100644 --- a/src/Message/ServerRequest.php +++ b/src/Message/ServerRequest.php @@ -23,7 +23,7 @@ class ServerRequest implements ServerRequestInterface /** @var array */ protected $cookieParams = []; - /** @var null|array|object */ + /** @var array|object|null */ protected $parsedBody; /** @var array */ @@ -147,7 +147,7 @@ public function withUploadedFiles(array $uploadedFiles): self } /** - * @return array|null|object + * @return array|object|null */ public function getParsedBody() { @@ -155,7 +155,7 @@ public function getParsedBody() } /** - * @param array|null|object $data + * @param array|object|null $data * * @throws InvalidArgumentException * diff --git a/src/Message/Stream.php b/src/Message/Stream.php index 48386d5..d8ae546 100644 --- a/src/Message/Stream.php +++ b/src/Message/Stream.php @@ -26,14 +26,14 @@ class Stream implements StreamInterface /** @var bool */ protected $writable; - /** @var array|mixed|null|void */ + /** @var array|mixed|void|null */ protected $uri; /** @var int */ protected $size; /** @var array Hash of readable and writable stream types */ - protected static $readWriteHash = [ + protected const READ_WRITE_HASH = [ 'read' => [ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, @@ -79,7 +79,7 @@ public function close(): void } /** - * @return null|resource + * @return resource|null */ public function detach() { @@ -320,7 +320,7 @@ public static function create($content = ''): StreamInterface } if (\is_string($content)) { - $resource = \fopen('php://temp', 'rw+b'); + $resource = \fopen('php://temp', 'rw+'); \fwrite($resource, $content); $content = $resource; } @@ -330,8 +330,8 @@ public static function create($content = ''): StreamInterface $obj->stream = $content; $meta = \stream_get_meta_data($obj->stream); $obj->seekable = $meta['seekable']; - $obj->readable = isset(self::$readWriteHash['read'][$meta['mode']]); - $obj->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $obj->readable = isset(static::READ_WRITE_HASH['read'][$meta['mode']]); + $obj->writable = isset(static::READ_WRITE_HASH['write'][$meta['mode']]); $obj->uri = $obj->getMetadata('uri'); return $obj; diff --git a/src/Message/UploadedFile.php b/src/Message/UploadedFile.php index 80855b8..227e4a4 100644 --- a/src/Message/UploadedFile.php +++ b/src/Message/UploadedFile.php @@ -15,15 +15,15 @@ class UploadedFile implements UploadedFileInterface { /** @var int[] */ - protected static $errors = [ - \UPLOAD_ERR_OK, - \UPLOAD_ERR_INI_SIZE, - \UPLOAD_ERR_FORM_SIZE, - \UPLOAD_ERR_PARTIAL, - \UPLOAD_ERR_NO_FILE, - \UPLOAD_ERR_NO_TMP_DIR, - \UPLOAD_ERR_CANT_WRITE, - \UPLOAD_ERR_EXTENSION + protected const ERRORS = [ + \UPLOAD_ERR_OK => 1, + \UPLOAD_ERR_INI_SIZE => 1, + \UPLOAD_ERR_FORM_SIZE => 1, + \UPLOAD_ERR_PARTIAL => 1, + \UPLOAD_ERR_NO_FILE => 1, + \UPLOAD_ERR_NO_TMP_DIR => 1, + \UPLOAD_ERR_CANT_WRITE => 1, + \UPLOAD_ERR_EXTENSION => 1, ]; /** @var string */ @@ -35,16 +35,16 @@ class UploadedFile implements UploadedFileInterface /** @var int */ protected $error; - /** @var null|string */ + /** @var string|null */ protected $file; /** @var bool */ protected $moved = false; - /** @var null|int */ + /** @var int|null */ protected $size; - /** @var null|StreamInterface */ + /** @var StreamInterface|null */ protected $stream; /** @var int */ @@ -53,11 +53,11 @@ class UploadedFile implements UploadedFileInterface /** * UploadedFile constructor. * - * @param mixed $streamOrFile - * @param int $size - * @param int $errorStatus - * @param string|null $clientFilename - * @param string|null $clientMediaType + * @param StreamInterface|string|resource $streamOrFile + * @param int $size + * @param int $errorStatus + * @param string|null $clientFilename + * @param string|null $clientMediaType * * @throws InvalidArgumentException */ @@ -92,7 +92,7 @@ public function getStream(): StreamInterface return $this->stream; } - $resource = \fopen($this->file, 'rb'); + $resource = \fopen($this->file, 'r'); return Stream::create($resource); } @@ -119,11 +119,11 @@ public function moveTo($targetPath): void } } else { $stream = $this->getStream(); - if ($stream->isSeekable() === true) { + if ($stream->isSeekable()) { $stream->rewind(); } - $this->copyToStream($stream, Stream::create(\fopen($targetPath, 'wb'))); + $this->copyToStream($stream, Stream::create(\fopen($targetPath, 'w'))); $this->moved = true; } @@ -149,7 +149,7 @@ public function getError(): int } /** - * @return null|string + * @return string|null */ public function getClientFilename(): ?string { @@ -157,7 +157,7 @@ public function getClientFilename(): ?string } /** - * @return null|string + * @return string|null */ public function getClientMediaType(): ?string { @@ -193,7 +193,7 @@ protected function setError($error): void throw new InvalidArgumentException('Upload file error status must be an integer'); } - if (!\in_array($error, self::$errors, true)) { + if (!isset(static::ERRORS[$error])) { throw new \InvalidArgumentException('Invalid error status for UploadedFile'); } @@ -267,7 +267,7 @@ protected function setClientMediaType($clientMediaType): void */ protected function isOk(): bool { - return $this->error === UPLOAD_ERR_OK; + return $this->error === \UPLOAD_ERR_OK; } /** diff --git a/src/Message/Uri.php b/src/Message/Uri.php index ba676bf..41eb877 100644 --- a/src/Message/Uri.php +++ b/src/Message/Uri.php @@ -13,16 +13,16 @@ class Uri implements UriInterface { /** @var array */ - protected static $schemes = [ + protected const SCHEMES = [ 'http' => 80, 'https' => 443, ]; /** @var string */ - protected static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + protected const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; /** @var string */ - protected static $charSubDelims = '!\$&\'\(\)\*\+,;='; + protected const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; /** @var string Uri scheme. */ protected $scheme = ''; @@ -307,7 +307,7 @@ public function withFragment($fragment): self */ public function __toString(): string { - return self::createUriString( + return static::createUriString( $this->scheme, $this->getAuthority(), $this->path, @@ -422,7 +422,7 @@ protected static function createUriString( */ protected static function isNonStandardPort(string $scheme, int $port): bool { - return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme]; + return !isset(static::SCHEMES[$scheme]) || $port !== static::SCHEMES[$scheme]; } /** @@ -475,7 +475,7 @@ protected function filterPort($port): ?int throw new InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 1 and 65535', $port)); } - if (!self::isNonStandardPort($this->scheme, $port)) { + if (!static::isNonStandardPort($this->scheme, $port)) { return null; } @@ -547,7 +547,7 @@ protected function isStringOrNull($param): bool */ protected function getPatternForFilteringPath(): string { - return '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/'; + return '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/'; } /** @@ -555,6 +555,6 @@ protected function getPatternForFilteringPath(): string */ protected function getPatternForFilteringQueryAndFragment(): string { - return '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/'; + return '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/'; } } diff --git a/src/Psr/Client/ClientException.php b/src/Psr/Client/ClientException.php deleted file mode 100644 index c7f47c2..0000000 --- a/src/Psr/Client/ClientException.php +++ /dev/null @@ -1,7 +0,0 @@ -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(); + + /** + * 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); + + /** + * 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); + + /** + * 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 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); + + /** + * 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); + + /** + * 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); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ public function getBody(); + + /** + * 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); -} +} \ No newline at end of file diff --git a/src/Psr/Message/RequestFactoryInterface.php b/src/Psr/Message/RequestFactoryInterface.php index d8ed797..cad6baa 100644 --- a/src/Psr/Message/RequestFactoryInterface.php +++ b/src/Psr/Message/RequestFactoryInterface.php @@ -4,5 +4,15 @@ interface RequestFactoryInterface { + /** + * Create a new request. + * + * @param string $method The HTTP method associated with the request. + * @param UriInterface|string $uri The URI associated with the request. If + * the value is a string, the factory MUST create a UriInterface + * instance based on it. + * + * @return RequestInterface + */ public function createRequest(string $method, $uri): RequestInterface; -} +} \ No newline at end of file diff --git a/src/Psr/Message/RequestInterface.php b/src/Psr/Message/RequestInterface.php index 0b5aaf1..5b1c9c0 100644 --- a/src/Psr/Message/RequestInterface.php +++ b/src/Psr/Message/RequestInterface.php @@ -2,12 +2,128 @@ namespace Psr\Http\Message; +/** + * Representation of an outgoing, client-side request. + * + * Per the HTTP specification, this interface includes properties for + * each of the following: + * + * - Protocol version + * - HTTP method + * - URI + * - Headers + * - Message body + * + * During construction, implementations MUST attempt to set the Host header from + * a provided URI if no Host header is provided. + * + * Requests are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + */ interface RequestInterface extends MessageInterface { + /** + * 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 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 static + */ public function withRequestTarget($requestTarget); + + /** + * Retrieves the HTTP method of the request. + * + * @return string Returns the request method. + */ public function getMethod(); + + /** + * 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); + + /** + * 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(); + + /** + * 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 static + */ public function withUri(UriInterface $uri, $preserveHost = false); -} +} \ No newline at end of file diff --git a/src/Psr/Message/ResponseFactoryInterface.php b/src/Psr/Message/ResponseFactoryInterface.php index e3ea778..c3a7bb1 100644 --- a/src/Psr/Message/ResponseFactoryInterface.php +++ b/src/Psr/Message/ResponseFactoryInterface.php @@ -4,5 +4,15 @@ interface ResponseFactoryInterface { + /** + * Create a new response. + * + * @param int $code HTTP status code; defaults to 200 + * @param string $reasonPhrase Reason phrase to associate with status code + * in generated response; if none is provided implementations MAY use + * the defaults as suggested in the HTTP specification. + * + * @return ResponseInterface + */ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface; -} +} \ No newline at end of file diff --git a/src/Psr/Message/ResponseInterface.php b/src/Psr/Message/ResponseInterface.php index 3483410..5f7344b 100644 --- a/src/Psr/Message/ResponseInterface.php +++ b/src/Psr/Message/ResponseInterface.php @@ -2,9 +2,67 @@ namespace Psr\Http\Message; +/** + * Representation of an outgoing, server-side response. + * + * Per the HTTP specification, this interface includes properties for + * each of the following: + * + * - Protocol version + * - Status code and reason phrase + * - Headers + * - Message body + * + * Responses are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + */ interface ResponseInterface extends MessageInterface { + /** + * 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 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 = ''); + + /** + * 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(); -} +} \ No newline at end of file diff --git a/src/Psr/Message/ServerRequestFactoryInterface.php b/src/Psr/Message/ServerRequestFactoryInterface.php index fbf8ddf..9ed626a 100644 --- a/src/Psr/Message/ServerRequestFactoryInterface.php +++ b/src/Psr/Message/ServerRequestFactoryInterface.php @@ -4,5 +4,21 @@ interface ServerRequestFactoryInterface { + /** + * Create a new server request. + * + * Note that server-params are taken precisely as given - no parsing/processing + * of the given values is performed, and, in particular, no attempt is made to + * determine the HTTP method or URI, which must be provided explicitly. + * + * @param string $method The HTTP method associated with the request. + * @param UriInterface|string $uri The URI associated with the request. If + * the value is a string, the factory MUST create a UriInterface + * instance based on it. + * @param array $serverParams Array of SAPI parameters with which to seed + * the generated request instance. + * + * @return ServerRequestInterface + */ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface; -} +} \ No newline at end of file diff --git a/src/Psr/Message/ServerRequestInterface.php b/src/Psr/Message/ServerRequestInterface.php index 49bf4be..42baf11 100644 --- a/src/Psr/Message/ServerRequestInterface.php +++ b/src/Psr/Message/ServerRequestInterface.php @@ -2,19 +2,260 @@ namespace Psr\Http\Message; +/** + * Representation of an incoming, server-side HTTP request. + * + * Per the HTTP specification, this interface includes properties for + * each of the following: + * + * - Protocol version + * - HTTP method + * - URI + * - Headers + * - Message body + * + * Additionally, it encapsulates all data as it has arrived to the + * application from the CGI and/or PHP environment, including: + * + * - The values represented in $_SERVER. + * - Any cookies provided (generally via $_COOKIE) + * - Query string arguments (generally via $_GET, or as parsed via parse_str()) + * - Upload files, if any (as represented by $_FILES) + * - Deserialized body parameters (generally from $_POST) + * + * $_SERVER values MUST be treated as immutable, as they represent application + * state at the time of request; as such, no methods are provided to allow + * modification of those values. The other values provide such methods, as they + * can be restored from $_SERVER or the request body, and may need treatment + * during the application (e.g., body parameters may be deserialized based on + * content type). + * + * Additionally, this interface recognizes the utility of introspecting a + * request to derive and match additional parameters (e.g., via URI path + * matching, decrypting cookie values, deserializing non-form-encoded body + * content, matching authorization headers to users, etc). These parameters + * are stored in an "attributes" property. + * + * Requests are considered immutable; all methods that might change state MUST + * be implemented such that they retain the internal state of the current + * message and return an instance that contains the changed state. + */ interface ServerRequestInterface extends RequestInterface { + /** + * 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(); + + /** + * 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 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); + + /** + * 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(); + + /** + * 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); + + /** + * 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(); + + /** + * 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); + + /** + * 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(); + + /** + * 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); + + /** + * 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(); + + /** + * 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 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); + + /** + * 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); -} +} \ No newline at end of file diff --git a/src/Psr/Message/StreamFactoryInterface.php b/src/Psr/Message/StreamFactoryInterface.php index a513ce6..74298fa 100644 --- a/src/Psr/Message/StreamFactoryInterface.php +++ b/src/Psr/Message/StreamFactoryInterface.php @@ -4,7 +4,40 @@ interface StreamFactoryInterface { + /** + * Create a new stream from a string. + * + * The stream SHOULD be created with a temporary resource. + * + * @param string $content String content with which to populate the stream. + * + * @return StreamInterface + */ public function createStream(string $content = ''): StreamInterface; + + /** + * Create a stream from an existing file. + * + * The file MUST be opened using the given mode, which may be any mode + * supported by the `fopen` function. + * + * The `$filename` MAY be any string supported by `fopen()`. + * + * @param string $filename Filename or stream URI to use as basis of stream. + * @param string $mode Mode with which to open the underlying filename/stream. + * + * @return StreamInterface + */ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface; + + /** + * Create a new stream from an existing resource. + * + * The stream MUST be readable and may be writable. + * + * @param resource $resource PHP resource to use as basis of stream. + * + * @return StreamInterface + */ public function createStreamFromResource($resource): StreamInterface; -} +} \ No newline at end of file diff --git a/src/Psr/Message/StreamInterface.php b/src/Psr/Message/StreamInterface.php index a9cced5..c7b3368 100644 --- a/src/Psr/Message/StreamInterface.php +++ b/src/Psr/Message/StreamInterface.php @@ -2,21 +2,157 @@ namespace Psr\Http\Message; +/** + * Describes a data stream. + * + * Typically, an instance will wrap a PHP stream; this interface provides + * a wrapper around the most common operations, including serialization of + * the entire stream to a string. + */ interface StreamInterface { + /** + * 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(); + + /** + * Closes the stream and any underlying resources. + * + * @return void + */ public function close(); + + /** + * 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(); + + /** + * 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(); + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * @throws \RuntimeException on error. + */ public function tell(); + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ public function eof(); + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ public function isSeekable(); + + /** + * 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); + + /** + * 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(); + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ public function isWritable(); + + /** + * 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); + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ public function isReadable(); + + /** + * 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); + + /** + * Returns the remaining contents in a string + * + * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. + */ public function getContents(); + + /** + * 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); -} +} \ No newline at end of file diff --git a/src/Psr/Message/UploadedFileFactoryInterface.php b/src/Psr/Message/UploadedFileFactoryInterface.php index f024451..e141121 100644 --- a/src/Psr/Message/UploadedFileFactoryInterface.php +++ b/src/Psr/Message/UploadedFileFactoryInterface.php @@ -4,6 +4,26 @@ interface UploadedFileFactoryInterface { + /** + * Create a new uploaded file. + * + * If a size is not provided it will be determined by checking the size of + * the file. + * + * @see http://php.net/manual/features.file-upload.post-method.php + * @see http://php.net/manual/features.file-upload.errors.php + * + * @param StreamInterface $stream Underlying stream representing the + * uploaded file content. + * @param int $size in bytes + * @param int $error PHP file upload error + * @param string $clientFilename Filename as provided by the client, if any. + * @param string $clientMediaType Media type as provided by the client, if any. + * + * @return UploadedFileInterface + * + * @throws \InvalidArgumentException If the file resource is not readable. + */ public function createUploadedFile( StreamInterface $stream, int $size = null, @@ -11,4 +31,4 @@ public function createUploadedFile( string $clientFilename = null, string $clientMediaType = null ): UploadedFileInterface; -} +} \ No newline at end of file diff --git a/src/Psr/Message/UploadedFileInterface.php b/src/Psr/Message/UploadedFileInterface.php index 54c31d2..eba5756 100644 --- a/src/Psr/Message/UploadedFileInterface.php +++ b/src/Psr/Message/UploadedFileInterface.php @@ -2,12 +2,122 @@ namespace Psr\Http\Message; +/** + * Value object representing a file uploaded through an HTTP request. + * + * Instances of this interface are considered immutable; all methods that + * might change state MUST be implemented such that they retain the internal + * state of the current instance and return an instance that contains the + * changed state. + */ interface UploadedFileInterface { + /** + * 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(); + + /** + * 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 $targetPath 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); + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); -} +} \ No newline at end of file diff --git a/src/Psr/Message/UriFactoryInterface.php b/src/Psr/Message/UriFactoryInterface.php index 9a645bd..348f34a 100644 --- a/src/Psr/Message/UriFactoryInterface.php +++ b/src/Psr/Message/UriFactoryInterface.php @@ -4,5 +4,14 @@ interface UriFactoryInterface { - public function createUri(string $uri = '') : UriInterface; -} + /** + * Create a new URI. + * + * @param string $uri + * + * @return UriInterface + * + * @throws \InvalidArgumentException If the given URI cannot be parsed. + */ + public function createUri(string $uri = ''): UriInterface; +} \ No newline at end of file diff --git a/src/Psr/Message/UriInterface.php b/src/Psr/Message/UriInterface.php index 713773f..6adb8fc 100644 --- a/src/Psr/Message/UriInterface.php +++ b/src/Psr/Message/UriInterface.php @@ -1,23 +1,323 @@ + * [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(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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 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 static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ public function withScheme($scheme); + + /** + * 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 static A new instance with the specified user information. + */ public function withUserInfo($user, $password = null); + + /** + * 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 static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ public function withHost($host); + + /** + * 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 static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ public function withPort($port); + + /** + * 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 static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ public function withPath($path); + + /** + * 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 static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ public function withQuery($query); + + /** + * 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 static A new instance with the specified fragment. + */ public function withFragment($fragment); + + /** + * 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(); -} +} \ No newline at end of file diff --git a/src/Psr/Server/MiddlewareInterface.php b/src/Psr/Server/MiddlewareInterface.php index 6741d40..0393475 100644 --- a/src/Psr/Server/MiddlewareInterface.php +++ b/src/Psr/Server/MiddlewareInterface.php @@ -5,7 +5,26 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +/** + * Participant in processing a server request and response. + * + * An HTTP middleware component participates in processing an HTTP message: + * by acting on the request, generating the response, or forwarding the + * request to a subsequent middleware and possibly acting on its response. + */ interface MiddlewareInterface { + /** + * Process an incoming server request. + * + * Processes an incoming server request in order to produce a response. + * If unable to produce the response itself, it may delegate to the provided + * request handler to do so. + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * + * @return ResponseInterface + */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; -} +} \ No newline at end of file diff --git a/src/Psr/Server/RequestHandlerInterface.php b/src/Psr/Server/RequestHandlerInterface.php index cbda5e8..7e38f1f 100644 --- a/src/Psr/Server/RequestHandlerInterface.php +++ b/src/Psr/Server/RequestHandlerInterface.php @@ -5,7 +5,22 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +/** + * Handles a server request and produces a response. + * + * An HTTP request handler process an HTTP request in order to produce an + * HTTP response. + */ interface RequestHandlerInterface { + /** + * Handles a request and produces a response. + * + * May call other collaborating code to generate the response. + * + * @param ServerRequestInterface $request + * + * @return ResponseInterface + */ public function handle(ServerRequestInterface $request): ResponseInterface; -} +} \ No newline at end of file From 451075b5276c72477a72c2b8756d7b2743c104b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Sun, 3 Mar 2019 21:26:53 +0100 Subject: [PATCH 3/9] update files with coding standard --- .php_cs | 1 + .travis.yml | 1 - composer.json | 1 - phpcs.xml | 2 +- run_all_commands.sh | 4 +- src/Message/Factory/Factory.php | 89 +++++++++++++++------------------ src/Message/MessageTrait.php | 67 ++++++++++++------------- src/Message/Request.php | 4 +- src/Message/RequestTrait.php | 17 +++---- src/Message/Response.php | 11 ++-- src/Message/ServerRequest.php | 25 ++++----- src/Message/Stream.php | 65 ++++++++++++------------ src/Message/UploadedFile.php | 45 ++++++++--------- src/Message/Uri.php | 49 +++++++++--------- 14 files changed, 178 insertions(+), 203 deletions(-) diff --git a/.php_cs b/.php_cs index 5ebdd8e..d3f1b34 100644 --- a/.php_cs +++ b/.php_cs @@ -25,6 +25,7 @@ return PhpCsFixer\Config::create() 'php_unit_strict' => true, 'phpdoc_order' => true, 'semicolon_after_instruction' => true, + 'single_import_per_statement' => false, 'strict_comparison' => true, 'strict_param' => true, 'concat_space' => ['spacing' => 'one'], diff --git a/.travis.yml b/.travis.yml index 6d8a303..dc80dc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ install: - composer install script: - - ./vendor/bin/phpcs -s - ./vendor/bin/php-cs-fixer fix --diff --dry-run - ./vendor/bin/phpunit --colors --coverage-text --coverage-clover build/logs/clover.xml diff --git a/composer.json b/composer.json index d3c6621..f2d8e02 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ }, "require-dev": { "phpunit/phpunit": "~6.2.0", - "squizlabs/php_codesniffer": "^3.0", "friendsofphp/php-cs-fixer": "^2.5", "satooshi/php-coveralls": "^1.0" }, diff --git a/phpcs.xml b/phpcs.xml index 083c4d4..9c50928 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -8,7 +8,7 @@ src */src/Psr/* - + diff --git a/run_all_commands.sh b/run_all_commands.sh index a16fa17..83a1fe3 100644 --- a/run_all_commands.sh +++ b/run_all_commands.sh @@ -1,4 +1,4 @@ -./vendor/bin/phpcbf -./vendor/bin/phpcs +#./vendor/bin/phpcbf +#./vendor/bin/phpcs ./vendor/bin/php-cs-fixer fix ./vendor/bin/phpunit --colors --coverage-html ./coverage \ No newline at end of file diff --git a/src/Message/Factory/Factory.php b/src/Message/Factory/Factory.php index 300206d..edaa968 100644 --- a/src/Message/Factory/Factory.php +++ b/src/Message/Factory/Factory.php @@ -4,26 +4,19 @@ namespace Rancoud\Http\Message\Factory; -use InvalidArgumentException; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestFactoryInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\StreamInterface; -use Psr\Http\Message\UploadedFileFactoryInterface; -use Psr\Http\Message\UploadedFileInterface; -use Psr\Http\Message\UriFactoryInterface; -use Psr\Http\Message\UriInterface; -use Rancoud\Http\Message\Request; -use Rancoud\Http\Message\Response; -use Rancoud\Http\Message\ServerRequest; -use Rancoud\Http\Message\Stream; -use Rancoud\Http\Message\UploadedFile; -use Rancoud\Http\Message\Uri; -use RuntimeException; +use Psr\Http\Message\{RequestFactoryInterface, + RequestInterface, + ResponseFactoryInterface, + ResponseInterface, + ServerRequestFactoryInterface, + ServerRequestInterface, + StreamFactoryInterface, + StreamInterface, + UploadedFileFactoryInterface, + UploadedFileInterface, + UriFactoryInterface, + UriInterface}; +use Rancoud\Http\Message\{Request, Response, ServerRequest, Stream, UploadedFile, Uri}; /** * Class Factory. @@ -34,8 +27,8 @@ class Factory implements RequestFactoryInterface, ResponseFactoryInterface, Serv * @param string $method * @param $uri * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return RequestInterface */ @@ -48,8 +41,8 @@ public function createRequest(string $method, $uri): RequestInterface * @param int $code * @param string $reasonPhrase * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return ResponseInterface */ @@ -61,7 +54,7 @@ public function createResponse(int $code = 200, string $reasonPhrase = ''): Resp /** * @param string $content * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return StreamInterface */ @@ -74,24 +67,24 @@ public function createStream(string $content = ''): StreamInterface * @param string $filename * @param string $mode * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return StreamInterface */ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface { if (!\file_exists($filename)) { - throw new RuntimeException(\sprintf('The file %s doesn\'t exist.', $filename)); + throw new \RuntimeException(\sprintf('The file %s doesn\'t exist.', $filename)); } if (!\in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { - throw new InvalidArgumentException(\sprintf('The mode %s is invalid.', $mode)); + throw new \InvalidArgumentException(\sprintf('The mode %s is invalid.', $mode)); } $resource = \fopen($filename, $mode); if ($resource === false) { - throw new RuntimeException(\sprintf('The file %s cannot be opened.', $filename)); + throw new \RuntimeException(\sprintf('The file %s cannot be opened.', $filename)); } return Stream::create($resource); @@ -100,7 +93,7 @@ public function createStreamFromFile(string $filename, string $mode = 'r'): Stre /** * @param $resource * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return StreamInterface */ @@ -116,7 +109,7 @@ public function createStreamFromResource($resource): StreamInterface * @param string|null $clientFilename * @param string|null $clientMediaType * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return UploadedFileInterface */ @@ -137,7 +130,7 @@ public function createUploadedFile( /** * @param string $uri * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return UriInterface */ @@ -149,7 +142,7 @@ public function createUri(string $uri = ''): UriInterface /** * @param array $server * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return UriInterface */ @@ -194,7 +187,7 @@ public function createUriFromArray(array $server): UriInterface * @param $uri * @param array $serverParams * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * @throws \RuntimeException * * @return ServerRequestInterface @@ -207,8 +200,8 @@ public function createServerRequest(string $method, $uri, array $serverParams = /** * @param array $server * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -232,8 +225,8 @@ public function createServerRequestFromArray(array $server): ServerRequestInterf * @param array $post * @param array $files * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -263,8 +256,8 @@ public function createServerRequestFromArrays( } /** - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -288,14 +281,14 @@ public function createServerRequestFromGlobals(): ServerRequestInterface /** * @param array $environment * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return string */ protected function getMethodFromEnvironment(array $environment): string { if (!isset($environment['REQUEST_METHOD'])) { - throw new InvalidArgumentException('Cannot determine HTTP method'); + throw new \InvalidArgumentException('Cannot determine HTTP method'); } return $environment['REQUEST_METHOD']; @@ -304,7 +297,7 @@ protected function getMethodFromEnvironment(array $environment): string /** * @param array $environment * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return UriInterface */ @@ -321,7 +314,7 @@ protected function getUriFromEnvironmentWithHTTP(array $environment): UriInterfa /** * @param array $files * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return array */ @@ -339,7 +332,7 @@ protected static function normalizeFiles(array $files): array continue; } else { - throw new InvalidArgumentException('Invalid value in files specification'); + throw new \InvalidArgumentException('Invalid value in files specification'); } } @@ -349,7 +342,7 @@ protected static function normalizeFiles(array $files): array /** * @param array $value * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return array|UploadedFile */ @@ -371,7 +364,7 @@ protected static function createUploadedFileFromSpec(array $value) /** * @param array $files * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return array */ diff --git a/src/Message/MessageTrait.php b/src/Message/MessageTrait.php index 3e72465..cbb309d 100644 --- a/src/Message/MessageTrait.php +++ b/src/Message/MessageTrait.php @@ -4,9 +4,7 @@ namespace Rancoud\Http\Message; -use InvalidArgumentException; use Psr\Http\Message\StreamInterface; -use Rancoud\Http\Message\Factory\Factory; /** * Trait MessageTrait. @@ -19,6 +17,9 @@ trait MessageTrait /** @var string */ protected static $patternHeaderValue = "@^[ \t\x21-\x7E\x80-\xFF]*$@"; + /** @var array */ + protected static $validProtocols = ['0.9', '1.0', '1.1', '2']; + /** @var array */ protected $headers = []; @@ -28,7 +29,7 @@ trait MessageTrait /** @var string */ protected $protocol = '1.1'; - /** @var StreamInterface */ + /** @var StreamInterface|null */ protected $stream; /** @@ -42,7 +43,7 @@ public function getProtocolVersion(): string /** * @param string $version * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -71,14 +72,14 @@ public function getHeaders(): array /** * @param string $name * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return bool */ public function hasHeader($name): bool { if (!\is_string($name)) { - throw new InvalidArgumentException('Header name must be a string'); + throw new \InvalidArgumentException('Header name must be a string'); } return isset($this->headerNames[\mb_strtolower($name)]); @@ -87,19 +88,19 @@ public function hasHeader($name): bool /** * @param string $name * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return array */ public function getHeader($name): array { if (!\is_string($name)) { - throw new InvalidArgumentException('Header name must be a string'); + throw new \InvalidArgumentException('Header name must be a string'); } $name = \mb_strtolower($name); - if (!isset($this->headerNames[\mb_strtolower($name)])) { + if (!isset($this->headerNames[$name])) { return []; } @@ -111,31 +112,31 @@ public function getHeader($name): array /** * @param string $name * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return string */ public function getHeaderLine($name): string { if (!\is_string($name)) { - throw new InvalidArgumentException('Header name must be a string'); + throw new \InvalidArgumentException('Header name must be a string'); } return \implode(', ', $this->getHeader($name)); } /** - * @param string $name - * @param mixed $value + * @param string $name + * @param string|int|float|array $value * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withHeader($name, $value): self { if (!\is_string($name) || $name === '') { - throw new InvalidArgumentException('Header name must be non-empty string'); + throw new \InvalidArgumentException('Header name must be non-empty string'); } $value = $this->validateAndTrimHeader($name, $value); @@ -152,17 +153,17 @@ public function withHeader($name, $value): self } /** - * @param string $name - * @param mixed $value + * @param string $name + * @param string|int|float|array $value * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withAddedHeader($name, $value): self { if (!\is_string($name) || $name === '') { - throw new InvalidArgumentException('Header name must be non-empty string'); + throw new \InvalidArgumentException('Header name must be non-empty string'); } $new = clone $this; @@ -174,14 +175,14 @@ public function withAddedHeader($name, $value): self /** * @param string $name * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withoutHeader($name): self { if (!\is_string($name) || $name === '') { - throw new InvalidArgumentException('Header name must be non-empty string'); + throw new \InvalidArgumentException('Header name must be non-empty string'); } $normalized = \mb_strtolower($name); @@ -199,14 +200,12 @@ public function withoutHeader($name): self } /** - * @throws InvalidArgumentException - * * @return StreamInterface */ public function getBody(): StreamInterface { if (!$this->stream) { - $this->stream = (new Factory())->createStream(''); + $this->stream = Stream::create(''); } return $this->stream; @@ -232,7 +231,7 @@ public function withBody(StreamInterface $body): self /** * @param array $headers * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function setHeaders(array $headers): void { @@ -253,32 +252,32 @@ protected function setHeaders(array $headers): void * @param string $header * @param mixed $values * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return array */ protected function validateAndTrimHeader($header, $values): array { if (!\is_string($header) || \preg_match(static::$patternHeaderName, $header) !== 1) { - throw new InvalidArgumentException('Header name must be RFC 7230 compatible string.'); + throw new \InvalidArgumentException('Header name must be RFC 7230 compatible string.'); } if (!\is_array($values)) { if ((!\is_numeric($values) && !\is_string($values)) || \preg_match(static::$patternHeaderValue, (string) $values) !== 1) { - throw new InvalidArgumentException('Header value must be RFC 7230 compatible string.'); + throw new \InvalidArgumentException('Header value must be RFC 7230 compatible string.'); } return [\trim((string) $values, " \t")]; } if (empty($values)) { - throw new InvalidArgumentException('Header values must be a string or an array of strings, empty array given.'); + throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.'); } $returnValues = []; foreach ($values as $v) { if ((!\is_numeric($v) && !\is_string($v)) || \preg_match(static::$patternHeaderValue, (string) $v) !== 1) { - throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); } $returnValues[] = \trim((string) $v, " \t"); @@ -287,17 +286,15 @@ protected function validateAndTrimHeader($header, $values): array return $returnValues; } + /** * @param string $protocolVersion - * - * @throws InvalidArgumentException - * * @return string */ protected function validateProtocolVersion(string $protocolVersion): string { - if (!\in_array($protocolVersion, ['0.9', '1.0', '1.1', '2'], true)) { - throw new InvalidArgumentException('Protocol Version must be 0.9 or 1.0 or 1.1 or 2'); + if (!\in_array($protocolVersion, static::$validProtocols, true)) { + throw new \InvalidArgumentException('Protocol Version must be 0.9 or 1.0 or 1.1 or 2'); } return $protocolVersion; diff --git a/src/Message/Request.php b/src/Message/Request.php index 373c683..439da56 100644 --- a/src/Message/Request.php +++ b/src/Message/Request.php @@ -4,9 +4,7 @@ namespace Rancoud\Http\Message; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; -use Psr\Http\Message\UriInterface; +use Psr\Http\Message\{RequestInterface, StreamInterface, UriInterface}; /** * Class Request. diff --git a/src/Message/RequestTrait.php b/src/Message/RequestTrait.php index 4152299..47cfd45 100644 --- a/src/Message/RequestTrait.php +++ b/src/Message/RequestTrait.php @@ -4,7 +4,6 @@ namespace Rancoud\Http\Message; -use InvalidArgumentException; use Psr\Http\Message\UriInterface; /** @@ -99,14 +98,14 @@ public function getRequestTarget(): string /** * @param $requestTarget * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withRequestTarget($requestTarget): self { if (\preg_match('#\s#', $requestTarget)) { - throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); + throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); } $new = clone $this; @@ -126,7 +125,7 @@ public function getMethod(): string /** * @param string $method * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -152,14 +151,14 @@ public function getUri(): UriInterface * @param UriInterface $uri * @param bool $preserveHost * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withUri(UriInterface $uri, $preserveHost = false): self { if (!\is_bool($preserveHost)) { - throw new InvalidArgumentException('Preserve Host must be a boolean'); + throw new \InvalidArgumentException('Preserve Host must be a boolean'); } if ($uri === $this->uri) { @@ -179,16 +178,16 @@ public function withUri(UriInterface $uri, $preserveHost = false): self /** * @param string $method * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function filterMethod($method): void { if (!\is_string($method)) { - throw new InvalidArgumentException('Method must be a string'); + throw new \InvalidArgumentException('Method must be a string'); } if (!\in_array($method, static::$methods, true)) { - throw new InvalidArgumentException(\sprintf('Method %s is invalid', $method)); + throw new \InvalidArgumentException(\sprintf('Method %s is invalid', $method)); } } diff --git a/src/Message/Response.php b/src/Message/Response.php index 4f8720a..1c02f30 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -4,7 +4,6 @@ namespace Rancoud\Http\Message; -use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; /** @@ -190,7 +189,7 @@ class Response implements ResponseInterface * @param string $version * @param string $reason * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public function __construct( int $status = 200, @@ -227,19 +226,19 @@ public function getStatusCode(): int * @param int $code * @param string $reasonPhrase * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return Response */ public function withStatus($code, $reasonPhrase = ''): self { if (!\is_int($code) && !\is_string($code)) { - throw new InvalidArgumentException('Status code has to be an integer'); + throw new \InvalidArgumentException('Status code has to be an integer'); } $code = (int) $code; if (!isset(static::PHRASES[$code])) { - throw new InvalidArgumentException('Status code has to be an integer between 100 and 599'); + throw new \InvalidArgumentException('Status code has to be an integer between 100 and 599'); } $new = clone $this; @@ -261,7 +260,7 @@ public function getReasonPhrase(): string } /** - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public function send() { diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php index caec5f9..8cce941 100644 --- a/src/Message/ServerRequest.php +++ b/src/Message/ServerRequest.php @@ -4,10 +4,7 @@ namespace Rancoud\Http\Message; -use InvalidArgumentException; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\UploadedFileInterface; -use Psr\Http\Message\UriInterface; +use Psr\Http\Message\{ServerRequestInterface, UploadedFileInterface, UriInterface}; /** * Class ServerRequest. @@ -45,7 +42,7 @@ class ServerRequest implements ServerRequestInterface * @param string $version * @param array $serverParams * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public function __construct( string $method, @@ -157,7 +154,7 @@ public function getParsedBody() /** * @param array|object|null $data * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -183,14 +180,14 @@ public function getAttributes(): array * @param string $name * @param mixed $default * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return mixed|null */ public function getAttribute($name, $default = null) { if (!\is_string($name)) { - throw new InvalidArgumentException('Name must be a string'); + throw new \InvalidArgumentException('Name must be a string'); } if (!\array_key_exists($name, $this->attributes)) { @@ -204,14 +201,14 @@ public function getAttribute($name, $default = null) * @param string $name * @param mixed $value * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withAttribute($name, $value): self { if (!\is_string($name)) { - throw new InvalidArgumentException('Name must be a string'); + throw new \InvalidArgumentException('Name must be a string'); } $new = clone $this; @@ -223,14 +220,14 @@ public function withAttribute($name, $value): self /** * @param string $name * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withoutAttribute($name): self { if (!\is_string($name)) { - throw new InvalidArgumentException('Name must be a string'); + throw new \InvalidArgumentException('Name must be a string'); } if (!\array_key_exists($name, $this->attributes)) { @@ -246,12 +243,12 @@ public function withoutAttribute($name): self /** * @param mixed $data * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function validateData($data): void { if (!\is_array($data) && !\is_object($data) && $data !== null) { - throw new InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); + throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); } } } diff --git a/src/Message/Stream.php b/src/Message/Stream.php index d8ae546..b19e304 100644 --- a/src/Message/Stream.php +++ b/src/Message/Stream.php @@ -4,10 +4,7 @@ namespace Rancoud\Http\Message; -use Exception; -use InvalidArgumentException; use Psr\Http\Message\StreamInterface; -use RuntimeException; /** * Class Stream. @@ -29,7 +26,7 @@ class Stream implements StreamInterface /** @var array|mixed|void|null */ protected $uri; - /** @var int */ + /** @var int|null */ protected $size; /** @var array Hash of readable and writable stream types */ @@ -63,7 +60,7 @@ public function __toString(): string } return $this->getContents(); - } catch (Exception $e) { + } catch (\Exception $e) { return ''; } } @@ -123,7 +120,7 @@ public function getSize(): ?int } /** - * @throws RuntimeException + * @throws \RuntimeException * * @return int */ @@ -132,7 +129,7 @@ public function tell(): int $result = \ftell($this->stream); if ($result === false) { - throw new RuntimeException('Unable to determine stream position'); + throw new \RuntimeException('Unable to determine stream position'); } return $result; @@ -158,31 +155,31 @@ public function isSeekable(): bool * @param int $offset * @param int $whence * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException */ public function seek($offset, $whence = \SEEK_SET): void { if (!\is_int($offset)) { - throw new InvalidArgumentException('Offset must be a int'); + throw new \InvalidArgumentException('Offset must be a int'); } if (!\is_int($whence)) { - throw new InvalidArgumentException('Whence must be a int'); + throw new \InvalidArgumentException('Whence must be a int'); } if (!$this->seekable) { - throw new RuntimeException('Stream is not seekable'); + throw new \RuntimeException('Stream is not seekable'); } elseif (\fseek($this->stream, $offset, $whence) === -1) { $whenceStr = \var_export($whence, true); $message = \sprintf('Unable to seek to stream position %d with whence %d', $offset, $whenceStr); - throw new RuntimeException($message); + throw new \RuntimeException($message); } } /** - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException */ public function rewind(): void { @@ -200,26 +197,26 @@ public function isWritable(): bool /** * @param string $string * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return bool|int */ public function write($string) { if (!\is_string($string)) { - throw new InvalidArgumentException('Data must be a string'); + throw new \InvalidArgumentException('Data must be a string'); } if (!$this->writable) { - throw new RuntimeException('Cannot write to a non-writable stream'); + throw new \RuntimeException('Cannot write to a non-writable stream'); } $this->size = null; $result = \fwrite($this->stream, $string); if ($result === false) { - throw new RuntimeException('Unable to write to stream'); + throw new \RuntimeException('Unable to write to stream'); } return $result; @@ -236,39 +233,39 @@ public function isReadable(): bool /** * @param $length * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return string */ public function read($length): string { if (!\is_int($length)) { - throw new InvalidArgumentException('Length must be a int'); + throw new \InvalidArgumentException('Length must be a int'); } if (!$this->readable) { - throw new RuntimeException('Cannot read from non-readable stream'); + throw new \RuntimeException('Cannot read from non-readable stream'); } return \fread($this->stream, $length); } /** - * @throws RuntimeException + * @throws \RuntimeException * * @return string */ public function getContents(): string { if (!isset($this->stream)) { - throw new RuntimeException('Unable to read stream contents'); + throw new \RuntimeException('Unable to read stream contents'); } $contents = \stream_get_contents($this->stream); if ($contents === false) { - throw new RuntimeException('Unable to read stream contents'); + throw new \RuntimeException('Unable to read stream contents'); } return $contents; @@ -277,14 +274,14 @@ public function getContents(): string /** * @param string|null $key * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return array|null */ public function getMetadata($key = null) { if (!$this->isStringOrNull($key)) { - throw new InvalidArgumentException('Key must be a string or NULL'); + throw new \InvalidArgumentException('Key must be a string or NULL'); } if (!isset($this->stream)) { @@ -307,9 +304,9 @@ public function getMetadata($key = null) } /** - * @param string $content + * @param string|resource|StreamInterface $content * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return StreamInterface */ @@ -325,7 +322,7 @@ public static function create($content = ''): StreamInterface $content = $resource; } - if ('resource' === \gettype($content)) { + if (\is_resource($content)) { $obj = new self(); $obj->stream = $content; $meta = \stream_get_meta_data($obj->stream); @@ -337,7 +334,7 @@ public static function create($content = ''): StreamInterface return $obj; } - throw new InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.'); + throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.'); } public function __destruct() @@ -352,6 +349,6 @@ public function __destruct() */ protected function isStringOrNull($param): bool { - return \in_array(\gettype($param), ['string', 'NULL'], true); + return $param === null || \is_string($param); } } diff --git a/src/Message/UploadedFile.php b/src/Message/UploadedFile.php index 227e4a4..0670475 100644 --- a/src/Message/UploadedFile.php +++ b/src/Message/UploadedFile.php @@ -4,10 +4,7 @@ namespace Rancoud\Http\Message; -use InvalidArgumentException; -use Psr\Http\Message\StreamInterface; -use Psr\Http\Message\UploadedFileInterface; -use RuntimeException; +use Psr\Http\Message\{StreamInterface, UploadedFileInterface}; /** * Class UploadedFile. @@ -59,7 +56,7 @@ class UploadedFile implements UploadedFileInterface * @param string|null $clientFilename * @param string|null $clientMediaType * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public function __construct( $streamOrFile, @@ -79,8 +76,8 @@ public function __construct( } /** - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException * * @return StreamInterface */ @@ -100,15 +97,15 @@ public function getStream(): StreamInterface /** * @param string $targetPath * - * @throws InvalidArgumentException - * @throws RuntimeException + * @throws \InvalidArgumentException + * @throws \RuntimeException */ public function moveTo($targetPath): void { $this->validateActive(); if (!$this->isStringNotEmpty($targetPath)) { - throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); + throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); } if ($this->file !== null) { @@ -128,7 +125,7 @@ public function moveTo($targetPath): void } if (!$this->moved) { - throw new RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath)); + throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath)); } } @@ -167,7 +164,7 @@ public function getClientMediaType(): ?string /** * @param $streamOrFile * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function setStreamOrFile($streamOrFile): void { @@ -178,19 +175,19 @@ protected function setStreamOrFile($streamOrFile): void } elseif ($streamOrFile instanceof StreamInterface) { $this->stream = $streamOrFile; } else { - throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile'); + throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile'); } } /** * @param int $error * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function setError($error): void { if (!\is_int($error)) { - throw new InvalidArgumentException('Upload file error status must be an integer'); + throw new \InvalidArgumentException('Upload file error status must be an integer'); } if (!isset(static::ERRORS[$error])) { @@ -203,12 +200,12 @@ protected function setError($error): void /** * @param int $size * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function setSize($size): void { if (!\is_int($size)) { - throw new InvalidArgumentException('Upload file size must be an integer'); + throw new \InvalidArgumentException('Upload file size must be an integer'); } $this->size = $size; @@ -237,12 +234,12 @@ protected function isStringNotEmpty($param): bool /** * @param string|null $clientFilename * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function setClientFilename($clientFilename): void { if (!$this->isStringOrNull($clientFilename)) { - throw new InvalidArgumentException('Upload file client filename must be a string or null'); + throw new \InvalidArgumentException('Upload file client filename must be a string or null'); } $this->clientFilename = $clientFilename; @@ -251,12 +248,12 @@ protected function setClientFilename($clientFilename): void /** * @param string|null $clientMediaType * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function setClientMediaType($clientMediaType): void { if (!$this->isStringOrNull($clientMediaType)) { - throw new InvalidArgumentException('Upload file client media type must be a string or null'); + throw new \InvalidArgumentException('Upload file client media type must be a string or null'); } $this->clientMediaType = $clientMediaType; @@ -271,16 +268,16 @@ protected function isOk(): bool } /** - * @throws RuntimeException + * @throws \RuntimeException */ protected function validateActive(): void { if (!$this->isOk()) { - throw new RuntimeException('Cannot retrieve stream due to upload error'); + throw new \RuntimeException('Cannot retrieve stream due to upload error'); } if ($this->moved) { - throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + throw new \RuntimeException('Cannot retrieve stream after it has already been moved'); } } diff --git a/src/Message/Uri.php b/src/Message/Uri.php index 41eb877..b4296ab 100644 --- a/src/Message/Uri.php +++ b/src/Message/Uri.php @@ -4,7 +4,6 @@ namespace Rancoud\Http\Message; -use InvalidArgumentException; use Psr\Http\Message\UriInterface; /** @@ -50,14 +49,14 @@ class Uri implements UriInterface * * @param string $uri * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ public function __construct(string $uri = '') { if ($uri !== '') { $parts = \parse_url($uri); if ($parts === false) { - throw new InvalidArgumentException(\sprintf('Unable to parse URI: %s', $uri)); + throw new \InvalidArgumentException(\sprintf('Unable to parse URI: %s', $uri)); } $this->applyParts($parts); @@ -145,7 +144,7 @@ public function getFragment(): string /** * @param string $scheme * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -168,18 +167,18 @@ public function withScheme($scheme): self * @param string $user * @param string|null $password * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ public function withUserInfo($user, $password = null): self { if (!\is_string($user)) { - throw new InvalidArgumentException('User must be a string'); + throw new \InvalidArgumentException('User must be a string'); } if (!$this->isStringOrNull($password)) { - throw new InvalidArgumentException('Password must be a string or NULL'); + throw new \InvalidArgumentException('Password must be a string or NULL'); } $info = $user; @@ -200,7 +199,7 @@ public function withUserInfo($user, $password = null): self /** * @param string $host * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -221,7 +220,7 @@ public function withHost($host): self /** * @param int|null $port * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -242,7 +241,7 @@ public function withPort($port): self /** * @param string $path * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -263,7 +262,7 @@ public function withPath($path): self /** * @param string $query * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -284,7 +283,7 @@ public function withQuery($query): self /** * @param string $fragment * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return self */ @@ -319,7 +318,7 @@ public function __toString(): string /** * @param array $parts * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException */ protected function applyParts(array $parts): void { @@ -428,14 +427,14 @@ protected static function isNonStandardPort(string $scheme, int $port): bool /** * @param string $scheme * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return string */ protected function filterScheme($scheme): string { if (!\is_string($scheme)) { - throw new InvalidArgumentException('Scheme must be a string'); + throw new \InvalidArgumentException('Scheme must be a string'); } return \mb_strtolower($scheme); @@ -444,14 +443,14 @@ protected function filterScheme($scheme): string /** * @param string $host * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return string */ protected function filterHost($host): string { if (!\is_string($host)) { - throw new InvalidArgumentException('Host must be a string'); + throw new \InvalidArgumentException('Host must be a string'); } return \mb_strtolower($host); @@ -460,7 +459,7 @@ protected function filterHost($host): string /** * @param int|null $port * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return int|null */ @@ -472,7 +471,7 @@ protected function filterPort($port): ?int $port = (int) $port; if ($port < 1 || $port > 65535) { - throw new InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 1 and 65535', $port)); + throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 1 and 65535', $port)); } if (!static::isNonStandardPort($this->scheme, $port)) { @@ -485,19 +484,19 @@ protected function filterPort($port): ?int /** * @param string $path * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return string */ protected function filterPath($path): string { if (!\is_string($path)) { - throw new InvalidArgumentException('Path must be a string'); + throw new \InvalidArgumentException('Path must be a string'); } return \preg_replace_callback( $this->getPatternForFilteringPath(), - [$this, 'rawurlencodeMatchZero'], + [__CLASS__, 'rawurlencodeMatchZero'], $path ); } @@ -505,19 +504,19 @@ protected function filterPath($path): string /** * @param string $str * - * @throws InvalidArgumentException + * @throws \InvalidArgumentException * * @return string */ protected function filterQueryAndFragment($str): string { if (!\is_string($str)) { - throw new InvalidArgumentException('Query and fragment must be a string'); + throw new \InvalidArgumentException('Query and fragment must be a string'); } return \preg_replace_callback( $this->getPatternForFilteringQueryAndFragment(), - [$this, 'rawurlencodeMatchZero'], + [__CLASS__, 'rawurlencodeMatchZero'], $str ); } From 18c188c9618e0dc450c638f7c22e068f592c6768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Sun, 3 Mar 2019 21:45:38 +0100 Subject: [PATCH 4/9] message trait ok --- run_all_commands.sh | 4 ++-- src/Message/Factory/Factory.php | 7 ------- src/Message/MessageTrait.php | 6 +++++- src/Message/Response.php | 1 + src/Message/UploadedFile.php | 2 ++ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/run_all_commands.sh b/run_all_commands.sh index 83a1fe3..a16fa17 100644 --- a/run_all_commands.sh +++ b/run_all_commands.sh @@ -1,4 +1,4 @@ -#./vendor/bin/phpcbf -#./vendor/bin/phpcs +./vendor/bin/phpcbf +./vendor/bin/phpcs ./vendor/bin/php-cs-fixer fix ./vendor/bin/phpunit --colors --coverage-html ./coverage \ No newline at end of file diff --git a/src/Message/Factory/Factory.php b/src/Message/Factory/Factory.php index edaa968..d32d9e7 100644 --- a/src/Message/Factory/Factory.php +++ b/src/Message/Factory/Factory.php @@ -28,7 +28,6 @@ class Factory implements RequestFactoryInterface, ResponseFactoryInterface, Serv * @param $uri * * @throws \InvalidArgumentException - * @throws \RuntimeException * * @return RequestInterface */ @@ -42,7 +41,6 @@ public function createRequest(string $method, $uri): RequestInterface * @param string $reasonPhrase * * @throws \InvalidArgumentException - * @throws \RuntimeException * * @return ResponseInterface */ @@ -188,7 +186,6 @@ public function createUriFromArray(array $server): UriInterface * @param array $serverParams * * @throws \InvalidArgumentException - * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -201,7 +198,6 @@ public function createServerRequest(string $method, $uri, array $serverParams = * @param array $server * * @throws \InvalidArgumentException - * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -226,7 +222,6 @@ public function createServerRequestFromArray(array $server): ServerRequestInterf * @param array $files * * @throws \InvalidArgumentException - * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -257,7 +252,6 @@ public function createServerRequestFromArrays( /** * @throws \InvalidArgumentException - * @throws \RuntimeException * * @return ServerRequestInterface */ @@ -268,7 +262,6 @@ public function createServerRequestFromGlobals(): ServerRequestInterface $server['REQUEST_METHOD'] = 'GET'; } - $headers = []; if (\function_exists('\getallheaders')) { $headers = \getallheaders(); } else { diff --git a/src/Message/MessageTrait.php b/src/Message/MessageTrait.php index cbb309d..73ce233 100644 --- a/src/Message/MessageTrait.php +++ b/src/Message/MessageTrait.php @@ -200,6 +200,8 @@ public function withoutHeader($name): self } /** + * @throws \InvalidArgumentException + * * @return StreamInterface */ public function getBody(): StreamInterface @@ -286,9 +288,11 @@ protected function validateAndTrimHeader($header, $values): array return $returnValues; } - /** * @param string $protocolVersion + * + * @throws \InvalidArgumentException + * * @return string */ protected function validateProtocolVersion(string $protocolVersion): string diff --git a/src/Message/Response.php b/src/Message/Response.php index 1c02f30..cceeaac 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -261,6 +261,7 @@ public function getReasonPhrase(): string /** * @throws \InvalidArgumentException + * @throws \RuntimeException */ public function send() { diff --git a/src/Message/UploadedFile.php b/src/Message/UploadedFile.php index 0670475..3564762 100644 --- a/src/Message/UploadedFile.php +++ b/src/Message/UploadedFile.php @@ -284,6 +284,8 @@ protected function validateActive(): void /** * @param StreamInterface $source * @param StreamInterface $dest + * + * @throws \RuntimeException */ protected function copyToStream(StreamInterface $source, StreamInterface $dest) { From 5dda1894c44f73130ae7aacc4720ff27ca1c21e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Sun, 3 Mar 2019 21:53:27 +0100 Subject: [PATCH 5/9] request* --- src/Message/RequestTrait.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Message/RequestTrait.php b/src/Message/RequestTrait.php index 47cfd45..6f9bc5a 100644 --- a/src/Message/RequestTrait.php +++ b/src/Message/RequestTrait.php @@ -85,11 +85,14 @@ public function getRequestTarget(): string } $target = $this->uri->getPath(); + $query = $this->uri->getQuery(); + if ($target === '') { $target = '/'; } - if ($this->uri->getQuery() !== '') { - $target .= '?' . $this->uri->getQuery(); + + if ($query !== '') { + $target .= '?' . $query; } return $target; @@ -194,7 +197,6 @@ protected function filterMethod($method): void protected function updateHostFromUri(): void { $host = $this->uri->getHost(); - if ($host === '') { return; } From b20e57721f6de1d41a3ed0cae15f82ae8c775a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Sun, 3 Mar 2019 22:27:14 +0100 Subject: [PATCH 6/9] fix response test when a string is provided for the contructor --- src/Message/Response.php | 21 ++++++++++++--------- tests/ResponseTest.php | 21 +++++++++++++++------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Message/Response.php b/src/Message/Response.php index cceeaac..2eb3023 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -4,7 +4,7 @@ namespace Rancoud\Http\Message; -use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\{ResponseInterface, StreamInterface}; /** * Class Response. @@ -183,11 +183,11 @@ class Response implements ResponseInterface /** * Response constructor. * - * @param int $status - * @param array $headers - * @param mixed $body - * @param string $version - * @param string $reason + * @param int $status + * @param array $headers + * @param string|resource|StreamInterface|null $body + * @param string $version + * @param string|null $reason * * @throws \InvalidArgumentException */ @@ -198,6 +198,10 @@ public function __construct( string $version = '1.1', string $reason = null ) { + if (!isset(static::PHRASES[$status])) { + throw new \InvalidArgumentException('Status code has to be an integer between 100 and 799'); + } + $this->statusCode = $status; if ($body !== '' && $body !== null) { @@ -232,13 +236,12 @@ public function getStatusCode(): int */ public function withStatus($code, $reasonPhrase = ''): self { - if (!\is_int($code) && !\is_string($code)) { + if (!\is_int($code)) { throw new \InvalidArgumentException('Status code has to be an integer'); } - $code = (int) $code; if (!isset(static::PHRASES[$code])) { - throw new \InvalidArgumentException('Status code has to be an integer between 100 and 599'); + throw new \InvalidArgumentException('Status code has to be an integer between 100 and 799'); } $new = clone $this; diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 32c789d..acc6032 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -40,14 +40,23 @@ public function testConstructorDoesNotReadStreamBody() $this->assertSame($body, $r->getBody()); } - public function testStatusCanBeNumericString() + public function testConstructStatusCantBeNumericString() { - $r = new Response('404'); - $r2 = $r->withStatus('201'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Status code has to be an integer'); + + $r = new Response('-404.4'); + } + + public function testWithStatusCantBeNumericString() + { + $r = new Response(404); $this->assertSame(404, $r->getStatusCode()); $this->assertSame('Not Found', $r->getReasonPhrase()); - $this->assertSame(201, $r2->getStatusCode()); - $this->assertSame('Created', $r2->getReasonPhrase()); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Status code has to be an integer'); + $r->withStatus('201'); } public function testCanConstructWithHeaders() @@ -381,7 +390,7 @@ public function testWithStatusCodeMustHaveCorrectType() public function testWithStatusReasonPhraseMustHaveCorrectType() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Status code has to be an integer between 100 and 599'); + $this->expectExceptionMessage('Status code has to be an integer between 100 and 799'); $r = new Response(); $r->withStatus(9, []); From 685b77e5d327f3acd8019422065c3caca3ca20b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Sun, 3 Mar 2019 23:49:15 +0100 Subject: [PATCH 7/9] updates files before factory --- README.md | 2 +- src/Message/Request.php | 4 +- src/Message/RequestTrait.php | 8 +++- src/Message/Response.php | 19 +++++---- src/Message/ServerRequest.php | 18 ++++----- src/Message/Stream.php | 8 ++-- src/Message/UploadedFile.php | 23 +++++------ src/Message/Uri.php | 75 +++++++++++++++++++++-------------- 8 files changed, 87 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index d44e6c8..3a977f6 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ $stream = (new Rancoud\Http\Message\Factory())->createStream('foobar'); * getReasonPhrase(): string * getStatusCode(): int * hasHeader(name: string): bool -* send(): void +* send([bodyChunkSize: int = 8192]): void * withAddedHeader(name: string, value: mixed): self * withBody(body: StreamInterface): self * withHeader(name: string, value: mixed): self diff --git a/src/Message/Request.php b/src/Message/Request.php index 439da56..b5a51af 100644 --- a/src/Message/Request.php +++ b/src/Message/Request.php @@ -15,8 +15,6 @@ class Request implements RequestInterface use RequestTrait; /** - * Request constructor. - * * @param string $method HTTP method * @param string|UriInterface $uri URI * @param array $headers Request headers @@ -36,7 +34,7 @@ public function __construct( $uri = new Uri($uri); } - $this->method = $method; + $this->method = $this->filterMethod($method); $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $this->validateProtocolVersion($version); diff --git a/src/Message/RequestTrait.php b/src/Message/RequestTrait.php index 6f9bc5a..c0a80a7 100644 --- a/src/Message/RequestTrait.php +++ b/src/Message/RequestTrait.php @@ -134,7 +134,7 @@ public function getMethod(): string */ public function withMethod($method): self { - $this->filterMethod($method); + $method = $this->filterMethod($method); $new = clone $this; $new->method = $method; @@ -182,8 +182,10 @@ public function withUri(UriInterface $uri, $preserveHost = false): self * @param string $method * * @throws \InvalidArgumentException + * + * @return string */ - protected function filterMethod($method): void + protected function filterMethod($method): string { if (!\is_string($method)) { throw new \InvalidArgumentException('Method must be a string'); @@ -192,6 +194,8 @@ protected function filterMethod($method): void if (!\in_array($method, static::$methods, true)) { throw new \InvalidArgumentException(\sprintf('Method %s is invalid', $method)); } + + return $method; } protected function updateHostFromUri(): void diff --git a/src/Message/Response.php b/src/Message/Response.php index 2eb3023..c8687ac 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -181,8 +181,6 @@ class Response implements ResponseInterface protected $statusCode = 200; /** - * Response constructor. - * * @param int $status * @param array $headers * @param string|resource|StreamInterface|null $body @@ -198,7 +196,8 @@ public function __construct( string $version = '1.1', string $reason = null ) { - if (!isset(static::PHRASES[$status])) { + $isStatusExist = isset(static::PHRASES[$status]); + if (!$isStatusExist) { throw new \InvalidArgumentException('Status code has to be an integer between 100 and 799'); } @@ -209,7 +208,7 @@ public function __construct( } $this->setHeaders($headers); - if (($reason === null || $reason === '') && isset(static::PHRASES[$this->statusCode])) { + if (($reason === null || $reason === '') && $isStatusExist) { $this->reasonPhrase = static::PHRASES[$status]; } else { $this->reasonPhrase = $reason; @@ -263,19 +262,23 @@ public function getReasonPhrase(): string } /** + * @param int $bodyChunkSize + * * @throws \InvalidArgumentException * @throws \RuntimeException */ - public function send() + public function send(int $bodyChunkSize = 8192): void { + $statusCode = $this->getStatusCode(); + $httpLine = \sprintf( 'HTTP/%s %s %s', $this->getProtocolVersion(), - $this->getStatusCode(), + $statusCode, $this->getReasonPhrase() ); - \header($httpLine, true, $this->getStatusCode()); + \header($httpLine, true, $statusCode); foreach ($this->getHeaders() as $name => $values) { foreach ($values as $value) { @@ -290,7 +293,7 @@ public function send() } while (!$stream->eof()) { - echo $stream->read(1024 * 8); + echo $stream->read($bodyChunkSize); } } } diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php index 8cce941..5963d01 100644 --- a/src/Message/ServerRequest.php +++ b/src/Message/ServerRequest.php @@ -4,7 +4,7 @@ namespace Rancoud\Http\Message; -use Psr\Http\Message\{ServerRequestInterface, UploadedFileInterface, UriInterface}; +use Psr\Http\Message\{ServerRequestInterface, StreamInterface, UploadedFileInterface, UriInterface}; /** * Class ServerRequest. @@ -33,14 +33,12 @@ class ServerRequest implements ServerRequestInterface protected $uploadedFiles = []; /** - * ServerRequest constructor. - * - * @param string $method - * @param mixed $uri - * @param array $headers - * @param mixed $body - * @param string $version - * @param array $serverParams + * @param string $method HTTP method + * @param string|UriInterface $uri URI + * @param array $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal * * @throws \InvalidArgumentException */ @@ -58,7 +56,7 @@ public function __construct( $uri = new Uri($uri); } - $this->method = $method; + $this->method = $this->filterMethod($method); $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $this->validateProtocolVersion($version); diff --git a/src/Message/Stream.php b/src/Message/Stream.php index b19e304..bbfd422 100644 --- a/src/Message/Stream.php +++ b/src/Message/Stream.php @@ -290,17 +290,15 @@ public function getMetadata($key = null) } return []; - } elseif ($key === null) { - return \stream_get_meta_data($this->stream); } $meta = \stream_get_meta_data($this->stream); - if (!isset($meta[$key])) { - return null; + if ($key === null) { + return $meta; } - return $meta[$key]; + return $meta[$key] ?? null; } /** diff --git a/src/Message/UploadedFile.php b/src/Message/UploadedFile.php index 3564762..22223db 100644 --- a/src/Message/UploadedFile.php +++ b/src/Message/UploadedFile.php @@ -11,7 +11,7 @@ */ class UploadedFile implements UploadedFileInterface { - /** @var int[] */ + /** @var array */ protected const ERRORS = [ \UPLOAD_ERR_OK => 1, \UPLOAD_ERR_INI_SIZE => 1, @@ -23,6 +23,9 @@ class UploadedFile implements UploadedFileInterface \UPLOAD_ERR_EXTENSION => 1, ]; + /** @var int */ + protected const DEFAULT_MAX_BYTES_LENGTH = 1048576; + /** @var string */ protected $clientFilename; @@ -44,12 +47,7 @@ class UploadedFile implements UploadedFileInterface /** @var StreamInterface|null */ protected $stream; - /** @var int */ - protected $defaultMaxBytesLength = 1048576; - /** - * UploadedFile constructor. - * * @param StreamInterface|string|resource $streamOrFile * @param int $size * @param int $errorStatus @@ -70,7 +68,7 @@ public function __construct( $this->setClientFilename($clientFilename); $this->setClientMediaType($clientMediaType); - if ($this->isOk()) { + if ($this->isUploadSuccess()) { $this->setStreamOrFile($streamOrFile); } } @@ -120,7 +118,8 @@ public function moveTo($targetPath): void $stream->rewind(); } - $this->copyToStream($stream, Stream::create(\fopen($targetPath, 'w'))); + $destination = Stream::create(\fopen($targetPath, 'w')); + $this->copyToStream($stream, $destination); $this->moved = true; } @@ -218,7 +217,7 @@ protected function setSize($size): void */ protected function isStringOrNull($param): bool { - return \in_array(\gettype($param), ['string', 'NULL'], true); + return $param === null || \is_string($param); } /** @@ -262,7 +261,7 @@ protected function setClientMediaType($clientMediaType): void /** * @return bool */ - protected function isOk(): bool + protected function isUploadSuccess(): bool { return $this->error === \UPLOAD_ERR_OK; } @@ -272,7 +271,7 @@ protected function isOk(): bool */ protected function validateActive(): void { - if (!$this->isOk()) { + if (!$this->isUploadSuccess()) { throw new \RuntimeException('Cannot retrieve stream due to upload error'); } @@ -290,7 +289,7 @@ protected function validateActive(): void protected function copyToStream(StreamInterface $source, StreamInterface $dest) { while (!$source->eof()) { - if (!$dest->write($source->read($this->defaultMaxBytesLength))) { + if (!$dest->write($source->read(static::DEFAULT_MAX_BYTES_LENGTH))) { break; } } diff --git a/src/Message/Uri.php b/src/Message/Uri.php index b4296ab..be0c391 100644 --- a/src/Message/Uri.php +++ b/src/Message/Uri.php @@ -45,8 +45,6 @@ class Uri implements UriInterface protected $fragment = ''; /** - * Uri constructor. - * * @param string $uri * * @throws \InvalidArgumentException @@ -173,15 +171,9 @@ public function withScheme($scheme): self */ public function withUserInfo($user, $password = null): self { - if (!\is_string($user)) { - throw new \InvalidArgumentException('User must be a string'); - } - - if (!$this->isStringOrNull($password)) { - throw new \InvalidArgumentException('Password must be a string or NULL'); - } + $info = $this->filterUser($user); + $password = $this->filterPass($password); - $info = $user; if ($password !== null && $password !== '') { $info .= ':' . $password; } @@ -335,7 +327,7 @@ protected function applyParts(array $parts): void } if (isset($parts['user'])) { - $this->userInfo = $parts['user']; + $this->userInfo = $this->filterUser($parts['user']); } if (isset($parts['host'])) { @@ -359,7 +351,7 @@ protected function applyParts(array $parts): void } if (isset($parts['pass'])) { - $this->userInfo .= ':' . $parts['pass']; + $this->userInfo .= ':' . $this->filterPass($parts['pass']); } } @@ -388,12 +380,15 @@ protected static function createUriString( $uri .= '//' . $authority; } + $charAtPosZero = \mb_substr($path, 0, 1); + $charAtPosOne = \mb_substr($path, 1, 1); + if ($path !== '') { - if ($path[0] !== '/') { + if ($charAtPosZero !== '/') { if ($authority !== '') { $path = '/' . $path; } - } elseif (isset($path[1]) && $path[1] === '/') { + } elseif (isset($charAtPosOne) && $charAtPosOne === '/') { if ($authority === '') { $path = '/' . \ltrim($path, '/'); } @@ -440,6 +435,38 @@ protected function filterScheme($scheme): string return \mb_strtolower($scheme); } + /** + * @param string $user + * + * @throws \InvalidArgumentException + * + * @return string + */ + protected function filterUser($user): string + { + if (!\is_string($user)) { + throw new \InvalidArgumentException('User must be a string'); + } + + return $user; + } + + /** + * @param ?string $pass + * + * @throws \InvalidArgumentException + * + * @return string + */ + protected function filterPass($pass): ?string + { + if ($pass !== null && !\is_string($pass)) { + throw new \InvalidArgumentException('Password must be a string or NULL'); + } + + return $pass; + } + /** * @param string $host * @@ -495,7 +522,7 @@ protected function filterPath($path): string } return \preg_replace_callback( - $this->getPatternForFilteringPath(), + static::getPatternForFilteringPath(), [__CLASS__, 'rawurlencodeMatchZero'], $path ); @@ -515,7 +542,7 @@ protected function filterQueryAndFragment($str): string } return \preg_replace_callback( - $this->getPatternForFilteringQueryAndFragment(), + static::getPatternForFilteringQueryAndFragment(), [__CLASS__, 'rawurlencodeMatchZero'], $str ); @@ -526,25 +553,15 @@ protected function filterQueryAndFragment($str): string * * @return string */ - protected function rawurlencodeMatchZero(array $match): string + protected static function rawurlencodeMatchZero(array $match): string { return \rawurlencode($match[0]); } - /** - * @param $param - * - * @return bool - */ - protected function isStringOrNull($param): bool - { - return \in_array(\gettype($param), ['string', 'NULL'], true); - } - /** * @return string */ - protected function getPatternForFilteringPath(): string + protected static function getPatternForFilteringPath(): string { return '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/'; } @@ -552,7 +569,7 @@ protected function getPatternForFilteringPath(): string /** * @return string */ - protected function getPatternForFilteringQueryAndFragment(): string + protected static function getPatternForFilteringQueryAndFragment(): string { return '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/'; } From b1da8861377d58e8303fddbba2e6da402d46ddc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Mon, 4 Mar 2019 00:38:40 +0100 Subject: [PATCH 8/9] add redirection and responseBody --- README.md | 2 ++ src/Message/Factory/Factory.php | 53 ++++++++++++++++++++++++--------- tests/FactoryTest.php | 26 ++++++++++++++-- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3a977f6..b9714ac 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ $stream = (new Rancoud\Http\Message\Factory())->createStream('foobar'); ### Methods * createRequest(method: string, uri: mixed): RequestInterface * createResponse([code: int = 200], [reasonPhrase: string = '']): ResponseInterface +* createResponseBody([code: int = 200], [body: mixed = null]): ResponseInterface +* createRedirection(location: string): ResponseInterface * createStream([content: string = '']): StreamInterface * createStreamFromFile(filename: string, [mode: string = 'r']): StreamInterface * createStreamFromResource(resource: mixed): StreamInterface diff --git a/src/Message/Factory/Factory.php b/src/Message/Factory/Factory.php index d32d9e7..6047c1d 100644 --- a/src/Message/Factory/Factory.php +++ b/src/Message/Factory/Factory.php @@ -24,8 +24,8 @@ class Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface { /** - * @param string $method - * @param $uri + * @param string $method + * @param string|UriInterface $uri * * @throws \InvalidArgumentException * @@ -49,6 +49,31 @@ public function createResponse(int $code = 200, string $reasonPhrase = ''): Resp return new Response($code, [], null, '1.1', $reasonPhrase); } + /** + * @param int $code + * @param string|resource|StreamInterface|null $body + * + * @throws \InvalidArgumentException + * + * @return Response + */ + public function createResponseBody(int $code = 200, $body = null): Response + { + return new Response($code, [], $body, '1.1'); + } + + /** + * @param string $location + * + * @throws \InvalidArgumentException + * + * @return Response + */ + public function createRedirection(string $location): Response + { + return new Response(301, ['Location' => $location], null, '1.1'); + } + /** * @param string $content * @@ -89,7 +114,7 @@ public function createStreamFromFile(string $filename, string $mode = 'r'): Stre } /** - * @param $resource + * @param resource $resource * * @throws \InvalidArgumentException * @@ -142,9 +167,9 @@ public function createUri(string $uri = ''): UriInterface * * @throws \InvalidArgumentException * - * @return UriInterface + * @return Uri */ - public function createUriFromArray(array $server): UriInterface + public function createUriFromArray(array $server): Uri { $uri = new Uri(''); @@ -181,9 +206,9 @@ public function createUriFromArray(array $server): UriInterface } /** - * @param string $method - * @param $uri - * @param array $serverParams + * @param string $method + * @param string|UriInterface $uri + * @param array $serverParams * * @throws \InvalidArgumentException * @@ -199,9 +224,9 @@ public function createServerRequest(string $method, $uri, array $serverParams = * * @throws \InvalidArgumentException * - * @return ServerRequestInterface + * @return ServerRequest */ - public function createServerRequestFromArray(array $server): ServerRequestInterface + public function createServerRequestFromArray(array $server): ServerRequest { return new ServerRequest( $this->getMethodFromEnvironment($server), @@ -223,7 +248,7 @@ public function createServerRequestFromArray(array $server): ServerRequestInterf * * @throws \InvalidArgumentException * - * @return ServerRequestInterface + * @return ServerRequest */ public function createServerRequestFromArrays( array $server, @@ -232,7 +257,7 @@ public function createServerRequestFromArrays( array $get, array $post, array $files - ): ServerRequestInterface { + ): ServerRequest { $method = $this->getMethodFromEnvironment($server); $uri = $this->getUriFromEnvironmentWithHTTP($server); @@ -253,9 +278,9 @@ public function createServerRequestFromArrays( /** * @throws \InvalidArgumentException * - * @return ServerRequestInterface + * @return ServerRequest */ - public function createServerRequestFromGlobals(): ServerRequestInterface + public function createServerRequestFromGlobals(): ServerRequest { $server = $_SERVER; if (!\array_key_exists('REQUEST_METHOD', $server)) { diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 6209c13..87b8b3a 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -30,7 +30,29 @@ public function testCreateResponse() $this->assertInstanceOf(StreamInterface::class, $r->getBody()); $this->assertSame('', (string) $r->getBody()); } - + + public function testCreateResponseBody() + { + $r = (new Factory())->createResponseBody(201, 'yolo'); + $this->assertSame(201, $r->getStatusCode()); + $this->assertSame('1.1', $r->getProtocolVersion()); + $this->assertSame('Created', $r->getReasonPhrase()); + $this->assertSame([], $r->getHeaders()); + $this->assertInstanceOf(StreamInterface::class, $r->getBody()); + $this->assertSame('yolo', (string) $r->getBody()); + } + + public function testCreateRedirection() + { + $r = (new Factory())->createRedirection('/blog/'); + $this->assertSame(301, $r->getStatusCode()); + $this->assertSame('1.1', $r->getProtocolVersion()); + $this->assertSame('Moved Permanently', $r->getReasonPhrase()); + $this->assertEquals(['Location' => ['/blog/']], $r->getHeaders()); + $this->assertInstanceOf(StreamInterface::class, $r->getBody()); + $this->assertSame('', (string) $r->getBody()); + } + public function testCreateServerRequest() { $r = (new Factory())->createServerRequest('POST', '/'); @@ -83,7 +105,7 @@ public function testCreateUri() $this->assertEquals('/aze/', $r->getPath()); } - public function testCreateUriFromArray() + public function testCreateUriFromServer() { $server = [ 'PHP_SELF' => '/blog/article.php', From 09ac843b360bf729f8753adb8b0187544e470765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rancoud?= Date: Mon, 4 Mar 2019 00:43:19 +0100 Subject: [PATCH 9/9] re-enable phpcs --- .travis.yml | 1 + composer.json | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index dc80dc7..6d8a303 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ install: - composer install script: + - ./vendor/bin/phpcs -s - ./vendor/bin/php-cs-fixer fix --diff --dry-run - ./vendor/bin/phpunit --colors --coverage-text --coverage-clover build/logs/clover.xml diff --git a/composer.json b/composer.json index f2d8e02..d3c6621 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ }, "require-dev": { "phpunit/phpunit": "~6.2.0", + "squizlabs/php_codesniffer": "^3.0", "friendsofphp/php-cs-fixer": "^2.5", "satooshi/php-coveralls": "^1.0" },