From 76c8c41d1a9f82b52eaaf799e6ad2f01f8c6c732 Mon Sep 17 00:00:00 2001 From: Anatoly Nekhay Date: Wed, 30 Oct 2024 01:51:42 +0100 Subject: [PATCH 1/4] v3.1 --- .scrutinizer.yml | 10 +- composer.json | 15 +- functions/server_request_files.php | 31 +-- functions/server_request_headers.php | 9 +- functions/server_request_method.php | 14 +- functions/server_request_protocol_version.php | 10 - functions/server_request_uri.php | 17 +- psalm.xml.dist | 1 + src/Exception/ExceptionInterface.php | 6 - src/Exception/InvalidArgumentException.php | 10 +- src/Exception/RuntimeException.php | 10 +- src/HeaderInterface.php | 39 ---- src/Message.php | 148 ++---------- src/Request.php | 106 ++------- src/Request/JsonRequest.php | 74 ++++++ src/RequestFactory.php | 11 +- src/Response.php | 74 +----- src/Response/HtmlResponse.php | 27 +-- src/Response/JsonResponse.php | 38 +-- src/ResponseFactory.php | 11 +- src/ServerRequest.php | 86 ++----- src/ServerRequestFactory.php | 16 +- src/Stream.php | 143 ++---------- src/Stream/FileStream.php | 32 +-- src/Stream/PhpInputStream.php | 34 ++- src/Stream/PhpMemoryStream.php | 15 -- src/Stream/PhpTempStream.php | 17 +- src/Stream/TempFileStream.php | 30 ++- src/Stream/TmpfileStream.php | 23 +- src/StreamFactory.php | 15 +- src/UploadedFile.php | 201 ++++------------ src/UploadedFileFactory.php | 14 +- src/Uri.php | 217 ++++-------------- src/Uri/Component/ComponentInterface.php | 6 - src/Uri/Component/Fragment.php | 32 +-- src/Uri/Component/Host.php | 32 +-- src/Uri/Component/Password.php | 36 +-- src/Uri/Component/Path.php | 32 +-- src/Uri/Component/Port.php | 23 +- src/Uri/Component/Query.php | 32 +-- src/Uri/Component/Scheme.php | 22 -- src/Uri/Component/User.php | 36 +-- src/Uri/Component/UserInfo.php | 24 +- src/UriFactory.php | 11 +- tests/BaseMessageTest.php | 42 ++-- tests/Request/JsonRequestTest.php | 54 +++++ tests/Response/HtmlResponseTest.php | 4 +- tests/Response/JsonResponseTest.php | 4 +- tests/ServerRequestFactoryTest.php | 4 +- tests/StreamTest.php | 7 +- tests/UploadedFileTest.php | 100 ++++---- 51 files changed, 535 insertions(+), 1470 deletions(-) create mode 100644 src/Request/JsonRequest.php create mode 100644 tests/Request/JsonRequestTest.php diff --git a/.scrutinizer.yml b/.scrutinizer.yml index ed0219b..0d791fe 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -3,19 +3,25 @@ build: nodes: analysis: environment: - php: 8.2 + php: 8.3 tests: override: - php-scrutinizer-run coverage: environment: - php: 8.2 + php: 8.3 tests: override: - command: XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-clover coverage.xml coverage: file: coverage.xml format: clover + php82: + environment: + php: 8.2 + tests: + override: + - command: php vendor/bin/phpunit php81: environment: php: 8.1 diff --git a/composer.json b/composer.json index 6600e85..3146dfb 100644 --- a/composer.json +++ b/composer.json @@ -36,9 +36,11 @@ "psr/http-message": "^1.0" }, "require-dev": { - "sunrise/coding-standard": "~1.0.0", - "phpunit/phpunit": "~9.5.0", - "php-http/psr7-integration-tests": "^1.1" + "php-http/psr7-integration-tests": "^1.4", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^9.6", + "sunrise/coding-standard": "^1.0", + "vimeo/psalm": "^5.26" }, "autoload": { "files": [ @@ -59,9 +61,9 @@ }, "scripts": { "test": [ - "phpcs", + "phpcs --colors", "psalm --no-cache", - "phpstan analyse src --level=9", + "phpstan analyse src --level=9 --memory-limit=-1", "XDEBUG_MODE=coverage phpunit --coverage-text --colors=always" ], "build": [ @@ -76,5 +78,8 @@ "sunrise/http-server-request": "*", "sunrise/stream": "*", "sunrise/uri": "*" + }, + "config": { + "sort-packages": true } } diff --git a/functions/server_request_files.php b/functions/server_request_files.php index 2f53581..ed1ee53 100644 --- a/functions/server_request_files.php +++ b/functions/server_request_files.php @@ -11,33 +11,14 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Sunrise\Http\Message\Stream\FileStream; -/** - * Import functions - */ use function is_array; -/** - * Import constants - */ use const UPLOAD_ERR_OK; use const UPLOAD_ERR_NO_FILE; /** - * Gets the request's uploaded files - * - * Please note that unsent files will not be handled, - * also note that if a file fails to upload successfully, - * a stream will not be created for it. - * - * @param array|null $files - * - * @return array - * * @link http://php.net/manual/en/reserved.variables.files.php * @link https://www.php.net/manual/ru/features.file-upload.post-method.php * @link https://www.php.net/manual/ru/features.file-upload.multiple.php @@ -49,22 +30,20 @@ function server_request_files(?array $files = null): array $walker = static function ($path, $size, $error, $name, $type) use (&$walker) { if (!is_array($path)) { - // It makes no sense to create a stream - // if the file has not been successfully uploaded. - $stream = UPLOAD_ERR_OK <> $error ? null : new FileStream($path, 'rb'); + $stream = $error === UPLOAD_ERR_OK ? new FileStream($path, 'rb') : null; return new UploadedFile($stream, $size, $error, $name, $type); } $result = []; foreach ($path as $key => $_) { - if (UPLOAD_ERR_NO_FILE <> $error[$key]) { + if ($error[$key] !== UPLOAD_ERR_NO_FILE) { $result[$key] = $walker( $path[$key], $size[$key], $error[$key], $name[$key], - $type[$key] + $type[$key], ); } } @@ -74,13 +53,13 @@ function server_request_files(?array $files = null): array $result = []; foreach ($files as $key => $file) { - if (UPLOAD_ERR_NO_FILE <> $file['error']) { + if ($file['error'] !== UPLOAD_ERR_NO_FILE) { $result[$key] = $walker( $file['tmp_name'], $file['size'], $file['error'], $file['name'], - $file['type'] + $file['type'], ); } } diff --git a/functions/server_request_headers.php b/functions/server_request_headers.php index 75f461e..bd1077c 100644 --- a/functions/server_request_headers.php +++ b/functions/server_request_headers.php @@ -11,9 +11,6 @@ namespace Sunrise\Http\Message; -/** - * Import functions - */ use function strncmp; use function strtolower; use function strtr; @@ -21,10 +18,6 @@ use function ucwords; /** - * Gets the request headers - * - * @param array|null $serverParams - * * @return array * * @link http://php.net/manual/en/reserved.variables.server.php @@ -46,7 +39,7 @@ function server_request_headers(?array $serverParams = null): array $result = []; foreach ($serverParams as $key => $value) { - if (0 <> strncmp('HTTP_', $key, 5)) { + if (strncmp('HTTP_', $key, 5) !== 0) { continue; } diff --git a/functions/server_request_method.php b/functions/server_request_method.php index 34c544a..56150b4 100644 --- a/functions/server_request_method.php +++ b/functions/server_request_method.php @@ -12,23 +12,11 @@ namespace Sunrise\Http\Message; /** - * Import classes - */ -use Fig\Http\Message\RequestMethodInterface; - -/** - * Gets the request method - * - * @param array|null $serverParams - * - * @return string - * - * @link http://php.net/manual/en/reserved.variables.server.php * @link https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.12 */ function server_request_method(?array $serverParams = null): string { $serverParams ??= $_SERVER; - return $serverParams['REQUEST_METHOD'] ?? RequestMethodInterface::METHOD_GET; + return $serverParams['REQUEST_METHOD'] ?? 'GET'; } diff --git a/functions/server_request_protocol_version.php b/functions/server_request_protocol_version.php index fdff200..2e9086c 100644 --- a/functions/server_request_protocol_version.php +++ b/functions/server_request_protocol_version.php @@ -11,20 +11,10 @@ namespace Sunrise\Http\Message; -/** - * Import functions - */ use function sprintf; use function sscanf; /** - * Gets the request's protocol version - * - * @param array|null $serverParams - * - * @return string - * - * @link http://php.net/manual/en/reserved.variables.server.php * @link https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.16 */ function server_request_protocol_version(?array $serverParams = null): string diff --git a/functions/server_request_uri.php b/functions/server_request_uri.php index dd3f9e4..09dd36b 100644 --- a/functions/server_request_uri.php +++ b/functions/server_request_uri.php @@ -11,31 +11,16 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\UriInterface; -/** - * Import functions - */ use function array_key_exists; -/** - * Gets the request URI - * - * @param array|null $serverParams - * - * @return UriInterface - * - * @link http://php.net/manual/en/reserved.variables.server.php - */ function server_request_uri(?array $serverParams = null): UriInterface { $serverParams ??= $_SERVER; if (array_key_exists('HTTPS', $serverParams)) { - if (! ('off' === $serverParams['HTTPS'])) { + if ('off' !== $serverParams['HTTPS']) { $scheme = 'https://'; } } diff --git a/psalm.xml.dist b/psalm.xml.dist index 38cce4c..fb42ac9 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -4,6 +4,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + phpVersion="7.4" > diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index 2a94002..f004ee4 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -11,14 +11,8 @@ namespace Sunrise\Http\Message\Exception; -/** - * Import classes - */ use Throwable; -/** - * ExceptionInterface - */ interface ExceptionInterface extends Throwable { } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 9626d8a..2c3862a 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -11,14 +11,6 @@ namespace Sunrise\Http\Message\Exception; -/** - * Import classes - */ -use InvalidArgumentException as BaseInvalidArgumentException; - -/** - * InvalidArgumentException - */ -class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 2c3a86d..0cfe6b1 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -11,14 +11,6 @@ namespace Sunrise\Http\Message\Exception; -/** - * Import classes - */ -use RuntimeException as BaseRuntimeException; - -/** - * RuntimeException - */ -class RuntimeException extends BaseRuntimeException implements ExceptionInterface +class RuntimeException extends \RuntimeException implements ExceptionInterface { } diff --git a/src/HeaderInterface.php b/src/HeaderInterface.php index ac5cc54..70159ae 100644 --- a/src/HeaderInterface.php +++ b/src/HeaderInterface.php @@ -11,9 +11,6 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use IteratorAggregate; /** @@ -21,53 +18,17 @@ */ interface HeaderInterface extends IteratorAggregate { - - /** - * Date format according to RFC-822 - * - * @var string - */ public const RFC822_DATE_FORMAT = 'D, d M y H:i:s O'; - /** - * Regular Expression used for a token validation according to RFC-7230 - * - * @var string - */ public const RFC7230_TOKEN_REGEX = '/^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+$/'; - /** - * Regular Expression used for a field-value validation according to RFC-7230 - * - * @var string - */ public const RFC7230_FIELD_VALUE_REGEX = '/^[\x09\x20-\x7E\x80-\xFF]*$/'; - /** - * Regular Expression used for a quoted-string validation according to RFC-7230 - * - * @var string - */ public const RFC7230_QUOTED_STRING_REGEX = '/^(?:[\x5C][\x22]|[\x09\x20\x21\x23-\x5B\x5D-\x7E\x80-\xFF])*$/'; - /** - * Gets the header field name - * - * @return string - */ public function getFieldName(): string; - /** - * Gets the header field value - * - * @return string - */ public function getFieldValue(): string; - /** - * Converts the header to a field - * - * @return string - */ public function __toString(): string; } diff --git a/src/Message.php b/src/Message.php index 71fb469..e803035 100644 --- a/src/Message.php +++ b/src/Message.php @@ -11,17 +11,11 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\MessageInterface; use Psr\Http\Message\StreamInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Stream\PhpTempStream; -/** - * Import functions - */ use function implode; use function in_array; use function is_array; @@ -30,61 +24,28 @@ use function sprintf; use function strtolower; -/** - * Hypertext Transfer Protocol Message - * - * @link https://tools.ietf.org/html/rfc7230 - * @link https://www.php-fig.org/psr/psr-7/ - */ abstract class Message implements MessageInterface { - - /** - * Allowed HTTP versions - * - * @var list - */ public const ALLOWED_HTTP_VERSIONS = ['1.0', '1.1', '2.0', '2']; - /** - * Default HTTP version - * - * @var string - */ - public const DEFAULT_HTTP_VERSION = '1.1'; + public const DEFAULT_HTTP_VERSION = self::ALLOWED_HTTP_VERSIONS[1]; - /** - * The message HTTP version - * - * @var string - */ private string $protocolVersion = self::DEFAULT_HTTP_VERSION; /** - * The message headers - * * @var array> */ private array $headers = []; /** - * Original header names - * * @var array */ private array $headerNames = []; - /** - * The message body - * - * @var StreamInterface|null - */ private ?StreamInterface $body = null; /** - * Gets the message HTTP version - * - * @return string + * @inheritDoc */ public function getProtocolVersion(): string { @@ -92,14 +53,9 @@ public function getProtocolVersion(): string } /** - * Creates a new instance of the message with the given HTTP version - * - * @param string $version - * - * @return static + * @inheritDoc * * @throws InvalidArgumentException - * If the HTTP version isn't valid. */ public function withProtocolVersion($version): MessageInterface { @@ -110,9 +66,7 @@ public function withProtocolVersion($version): MessageInterface } /** - * Gets the message headers - * - * @return array> + * @inheritDoc */ public function getHeaders(): array { @@ -120,11 +74,7 @@ public function getHeaders(): array } /** - * Checks if a header exists in the message by the given name - * - * @param string $name - * - * @return bool + * @inheritDoc */ public function hasHeader($name): bool { @@ -134,11 +84,7 @@ public function hasHeader($name): bool } /** - * Gets a header value(s) from the message by the given name - * - * @param string $name - * - * @return list + * @inheritDoc */ public function getHeader($name): array { @@ -152,11 +98,7 @@ public function getHeader($name): array } /** - * Gets a header value as a string from the message by the given name - * - * @param string $name - * - * @return string + * @inheritDoc */ public function getHeaderLine($name): string { @@ -170,15 +112,7 @@ public function getHeaderLine($name): string } /** - * Creates a new instance of the message with the given header overwriting the old header - * - * @param string $name - * @param string|string[] $value - * - * @return static - * - * @throws InvalidArgumentException - * If the header isn't valid. + * @inheritDoc */ public function withHeader($name, $value): MessageInterface { @@ -189,15 +123,7 @@ public function withHeader($name, $value): MessageInterface } /** - * Creates a new instance of the message with the given header NOT overwriting the old header - * - * @param string $name - * @param string|string[] $value - * - * @return static - * - * @throws InvalidArgumentException - * If the header isn't valid. + * @inheritDoc */ public function withAddedHeader($name, $value): MessageInterface { @@ -208,11 +134,7 @@ public function withAddedHeader($name, $value): MessageInterface } /** - * Creates a new instance of the message without a header by the given name - * - * @param string $name - * - * @return static + * @inheritDoc */ public function withoutHeader($name): MessageInterface { @@ -223,9 +145,7 @@ public function withoutHeader($name): MessageInterface } /** - * Gets the message body - * - * @return StreamInterface + * @inheritDoc */ public function getBody(): StreamInterface { @@ -233,11 +153,7 @@ public function getBody(): StreamInterface } /** - * Creates a new instance of the message with the given body - * - * @param StreamInterface $body - * - * @return static + * @inheritDoc */ public function withBody(StreamInterface $body): MessageInterface { @@ -252,10 +168,7 @@ public function withBody(StreamInterface $body): MessageInterface * * @param string $protocolVersion * - * @return void - * * @throws InvalidArgumentException - * If the HTTP version isn't valid. */ final protected function setProtocolVersion($protocolVersion): void { @@ -269,12 +182,8 @@ final protected function setProtocolVersion($protocolVersion): void * * @param string $name * @param string|string[] $value - * @param bool $replace - * - * @return void * * @throws InvalidArgumentException - * If the header isn't valid. */ final protected function setHeader($name, $value, bool $replace = true): void { @@ -285,9 +194,7 @@ final protected function setHeader($name, $value, bool $replace = true): void $this->validateHeaderName($name); $this->validateHeaderValue($name, $value); - if ($replace) { - $this->deleteHeader($name); - } + $replace and $this->deleteHeader($name); $key = strtolower($name); @@ -304,10 +211,7 @@ final protected function setHeader($name, $value, bool $replace = true): void * * @param array $headers * - * @return void - * * @throws InvalidArgumentException - * If one of the headers isn't valid. */ final protected function setHeaders(array $headers): void { @@ -320,8 +224,6 @@ final protected function setHeaders(array $headers): void * Deletes a header from the message by the given name * * @param string $name - * - * @return void */ final protected function deleteHeader($name): void { @@ -335,10 +237,6 @@ final protected function deleteHeader($name): void /** * Sets the given body to the message - * - * @param StreamInterface $body - * - * @return void */ final protected function setBody(StreamInterface $body): void { @@ -350,10 +248,7 @@ final protected function setBody(StreamInterface $body): void * * @param mixed $protocolVersion * - * @return void - * * @throws InvalidArgumentException - * If the HTTP version isn't valid. */ private function validateProtocolVersion($protocolVersion): void { @@ -367,10 +262,7 @@ private function validateProtocolVersion($protocolVersion): void * * @param mixed $name * - * @return void - * * @throws InvalidArgumentException - * If the header name isn't valid. */ private function validateHeaderName($name): void { @@ -390,31 +282,27 @@ private function validateHeaderName($name): void /** * Validates the given header value * - * @param string $name * @param array $value * - * @return void - * * @throws InvalidArgumentException - * If the header value isn't valid. */ private function validateHeaderValue(string $name, array $value): void { - if ([] === $value) { + if ($value === []) { throw new InvalidArgumentException(sprintf( - 'The "%s" HTTP header value cannot be an empty array', + 'The value of the HTTP header "%s" cannot be an empty array', $name, )); } foreach ($value as $key => $item) { - if ('' === $item) { + if ($item === '') { continue; } if (!is_string($item)) { throw new InvalidArgumentException(sprintf( - 'The "%s[%s]" HTTP header value must be a string', + 'The value of the HTTP header "%s[%s]" must be a string', $name, $key )); @@ -422,7 +310,7 @@ private function validateHeaderValue(string $name, array $value): void if (!preg_match(HeaderInterface::RFC7230_FIELD_VALUE_REGEX, $item)) { throw new InvalidArgumentException(sprintf( - 'The "%s[%s]" HTTP header value is invalid', + 'The value of the HTTP header "%s[%s]" is invalid', $name, $key )); diff --git a/src/Request.php b/src/Request.php index 345d1b9..c10164b 100644 --- a/src/Request.php +++ b/src/Request.php @@ -11,69 +11,29 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Fig\Http\Message\RequestMethodInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_match; use function strncmp; -/** - * HTTP Request Message - * - * @link https://tools.ietf.org/html/rfc7230 - * @link https://www.php-fig.org/psr/psr-7/ - */ class Request extends Message implements RequestInterface, RequestMethodInterface { - - /** - * Regular Expression used for a request target validation - * - * @var string - */ private const RFC7230_REQUEST_TARGET_REGEX = '/^[\x21-\x7E\x80-\xFF]+$/'; - /** - * The request method (aka verb) - * - * @var string - */ private string $method = self::METHOD_GET; - - /** - * The request URI - * - * @var UriInterface - */ private UriInterface $uri; - - /** - * The request target - * - * @var string|null - */ private ?string $requestTarget = null; /** - * Constructor of the class - * - * @param string|null $method * @param mixed $uri * @param array|null $headers - * @param StreamInterface|null $body * * @throws InvalidArgumentException - * If one of the arguments isn't valid. */ public function __construct( ?string $method = null, @@ -81,25 +41,23 @@ public function __construct( ?array $headers = null, ?StreamInterface $body = null ) { - if (isset($method)) { + if ($method !== null) { $this->setMethod($method); } $this->setUri($uri ?? '/'); - if (isset($headers)) { + if ($headers !== null) { $this->setHeaders($headers); } - if (isset($body)) { + if ($body !== null) { $this->setBody($body); } } /** - * Gets the request method - * - * @return string + * @inheritDoc */ public function getMethod(): string { @@ -107,14 +65,7 @@ public function getMethod(): string } /** - * Creates a new instance of the request with the given method - * - * @param string $method - * - * @return static - * - * @throws InvalidArgumentException - * If the method isn't valid. + * @inheritDoc */ public function withMethod($method): RequestInterface { @@ -125,9 +76,7 @@ public function withMethod($method): RequestInterface } /** - * Gets the request URI - * - * @return UriInterface + * @inheritDoc */ public function getUri(): UriInterface { @@ -135,12 +84,7 @@ public function getUri(): UriInterface } /** - * Creates a new instance of the request with the given URI - * - * @param UriInterface $uri - * @param bool $preserveHost - * - * @return static + * @inheritDoc */ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface { @@ -151,13 +95,11 @@ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterf } /** - * Gets the request target - * - * @return string + * @inheritDoc */ public function getRequestTarget(): string { - if (isset($this->requestTarget)) { + if ($this->requestTarget !== null) { return $this->requestTarget; } @@ -181,14 +123,7 @@ public function getRequestTarget(): string } /** - * Creates a new instance of the request with the given request target - * - * @param mixed $requestTarget - * - * @return static - * - * @throws InvalidArgumentException - * If the request target isn't valid. + * @inheritDoc */ public function withRequestTarget($requestTarget): RequestInterface { @@ -203,10 +138,7 @@ public function withRequestTarget($requestTarget): RequestInterface * * @param string $method * - * @return void - * * @throws InvalidArgumentException - * If the method isn't valid. */ final protected function setMethod($method): void { @@ -221,10 +153,7 @@ final protected function setMethod($method): void * @param mixed $uri * @param bool $preserveHost * - * @return void - * * @throws InvalidArgumentException - * If the URI isn't valid. */ final protected function setUri($uri, $preserveHost = false): void { @@ -240,7 +169,7 @@ final protected function setUri($uri, $preserveHost = false): void } $port = $this->uri->getPort(); - if (isset($port)) { + if ($port !== null) { $host .= ':' . $port; } @@ -252,10 +181,7 @@ final protected function setUri($uri, $preserveHost = false): void * * @param mixed $requestTarget * - * @return void - * * @throws InvalidArgumentException - * If the request target isn't valid. */ final protected function setRequestTarget($requestTarget): void { @@ -273,14 +199,11 @@ final protected function setRequestTarget($requestTarget): void * * @param mixed $method * - * @return void - * * @throws InvalidArgumentException - * If the method isn't valid. */ private function validateMethod($method): void { - if ('' === $method) { + if ($method === '') { throw new InvalidArgumentException('HTTP method cannot be an empty'); } @@ -298,14 +221,11 @@ private function validateMethod($method): void * * @param mixed $requestTarget * - * @return void - * * @throws InvalidArgumentException - * If the request target isn't valid. */ private function validateRequestTarget($requestTarget): void { - if ('' === $requestTarget) { + if ($requestTarget === '') { throw new InvalidArgumentException('HTTP request target cannot be an empty'); } diff --git a/src/Request/JsonRequest.php b/src/Request/JsonRequest.php new file mode 100644 index 0000000..f451109 --- /dev/null +++ b/src/Request/JsonRequest.php @@ -0,0 +1,74 @@ + + * @copyright Copyright (c) 2018, Anatoly Nekhay + * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE + * @link https://github.com/sunrise-php/http-message + */ + +namespace Sunrise\Http\Message\Request; + +use JsonException; +use Psr\Http\Message\StreamInterface; +use Sunrise\Http\Message\Exception\InvalidArgumentException; +use Sunrise\Http\Message\Request; +use Sunrise\Http\Message\Stream\PhpTempStream; + +use function json_encode; +use function sprintf; + +use const JSON_THROW_ON_ERROR; + +/** + * @since 3.1.0 + */ +final class JsonRequest extends Request +{ + /** + * @param mixed $uri + * @param mixed $data + * @param int<1, max> $depth + * @psalm-param int<1, 2147483647> $depth + * + * @throws InvalidArgumentException + */ + public function __construct(string $method, $uri, $data, int $flags = 0, int $depth = 512) + { + parent::__construct($method, $uri); + + $this->setBody(self::createBody($data, $flags, $depth)); + $this->setHeader('Content-Type', 'application/json; charset=utf-8'); + } + + /** + * @param mixed $data + * @param int<1, max> $depth + * @psalm-param int<1, 2147483647> $depth + * + * @throws InvalidArgumentException + */ + private static function createBody($data, int $flags, int $depth): StreamInterface + { + if ($data instanceof StreamInterface) { + return $data; + } + + try { + $json = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth); + } catch (JsonException $e) { + throw new InvalidArgumentException(sprintf( + 'Unable to create the JSON request due to an invalid data: %s', + $e->getMessage(), + ), 0, $e); + } + + $stream = new PhpTempStream('r+b'); + $stream->write($json); + $stream->rewind(); + + return $stream; + } +} diff --git a/src/RequestFactory.php b/src/RequestFactory.php index 1e49412..61ee728 100644 --- a/src/RequestFactory.php +++ b/src/RequestFactory.php @@ -11,22 +11,13 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; -/** - * HTTP Request Message Factory - * - * @link https://www.php-fig.org/psr/psr-17/ - */ class RequestFactory implements RequestFactoryInterface { - /** - * {@inheritdoc} + * @inheritDoc */ public function createRequest(string $method, $uri): RequestInterface { diff --git a/src/Response.php b/src/Response.php index 501cb36..8ed6ca8 100644 --- a/src/Response.php +++ b/src/Response.php @@ -11,30 +11,17 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_int; use function is_string; use function preg_match; -/** - * HTTP Response Message - * - * @link https://tools.ietf.org/html/rfc7230 - * @link https://www.php-fig.org/psr/psr-7/ - */ class Response extends Message implements ResponseInterface, StatusCodeInterface { - /** * List of Reason Phrases * @@ -43,13 +30,11 @@ class Response extends Message implements ResponseInterface, StatusCodeInterface * @var array, non-empty-string> */ public const REASON_PHRASES = [ - // 1xx 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 103 => 'Early Hints', - // 2xx 200 => 'OK', 201 => 'Created', @@ -61,7 +46,6 @@ class Response extends Message implements ResponseInterface, StatusCodeInterface 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', - // 3xx 300 => 'Multiple Choices', 301 => 'Moved Permanently', @@ -71,7 +55,6 @@ class Response extends Message implements ResponseInterface, StatusCodeInterface 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', - // 4xx 400 => 'Bad Request', 401 => 'Unauthorized', @@ -101,7 +84,6 @@ class Response extends Message implements ResponseInterface, StatusCodeInterface 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', - // 5xx 500 => 'Internal Server Error', 501 => 'Not Implemented', @@ -116,30 +98,13 @@ class Response extends Message implements ResponseInterface, StatusCodeInterface 511 => 'Network Authentication Required', ]; - /** - * The response's status code - * - * @var int - */ private int $statusCode = self::STATUS_OK; - - /** - * The response's reason phrase - * - * @var string - */ private string $reasonPhrase = self::REASON_PHRASES[self::STATUS_OK]; /** - * Constrictor of the class - * - * @param int|null $statusCode - * @param string|null $reasonPhrase * @param array|null $headers - * @param StreamInterface|null $body * * @throws InvalidArgumentException - * If one of the arguments isn't valid. */ public function __construct( ?int $statusCode = null, @@ -147,23 +112,21 @@ public function __construct( ?array $headers = null, ?StreamInterface $body = null ) { - if (isset($statusCode)) { + if ($statusCode !== null) { $this->setStatus($statusCode, $reasonPhrase ?? ''); } - if (isset($headers)) { + if ($headers !== null) { $this->setHeaders($headers); } - if (isset($body)) { + if ($body !== null) { $this->setBody($body); } } /** - * Gets the response's status code - * - * @return int + * @inheritDoc */ public function getStatusCode(): int { @@ -171,9 +134,7 @@ public function getStatusCode(): int } /** - * Gets the response's reason phrase - * - * @return string + * @inheritDoc */ public function getReasonPhrase(): string { @@ -181,15 +142,7 @@ public function getReasonPhrase(): string } /** - * Creates a new instance of the response with the given status code - * - * @param int $code - * @param string $reasonPhrase - * - * @return static - * - * @throws InvalidArgumentException - * If the status isn't valid. + * @inheritDoc */ public function withStatus($code, $reasonPhrase = ''): ResponseInterface { @@ -205,17 +158,14 @@ public function withStatus($code, $reasonPhrase = ''): ResponseInterface * @param int $statusCode * @param string $reasonPhrase * - * @return void - * * @throws InvalidArgumentException - * If the status isn't valid. */ final protected function setStatus($statusCode, $reasonPhrase): void { $this->validateStatusCode($statusCode); $this->validateReasonPhrase($reasonPhrase); - if ('' === $reasonPhrase) { + if ($reasonPhrase === '') { $reasonPhrase = self::REASON_PHRASES[$statusCode] ?? 'Unknown Status Code'; } @@ -230,10 +180,7 @@ final protected function setStatus($statusCode, $reasonPhrase): void * * @param mixed $statusCode * - * @return void - * * @throws InvalidArgumentException - * If the status code isn't valid. */ private function validateStatusCode($statusCode): void { @@ -241,7 +188,7 @@ private function validateStatusCode($statusCode): void throw new InvalidArgumentException('HTTP status code must be an integer'); } - if (! ($statusCode >= 100 && $statusCode <= 599)) { + if (!($statusCode >= 100 && $statusCode <= 599)) { throw new InvalidArgumentException('Invalid HTTP status code'); } } @@ -253,14 +200,11 @@ private function validateStatusCode($statusCode): void * * @param mixed $reasonPhrase * - * @return void - * * @throws InvalidArgumentException - * If the reason phrase isn't valid. */ private function validateReasonPhrase($reasonPhrase): void { - if ('' === $reasonPhrase) { + if ($reasonPhrase === '') { return; } diff --git a/src/Response/HtmlResponse.php b/src/Response/HtmlResponse.php index 3f7cead..8c154c5 100644 --- a/src/Response/HtmlResponse.php +++ b/src/Response/HtmlResponse.php @@ -11,31 +11,18 @@ namespace Sunrise\Http\Message\Response; -/** - * Import classes - */ use Psr\Http\Message\StreamInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Response; use Sunrise\Http\Message\Stream\PhpTempStream; -/** - * Import functions - */ use function is_object; use function is_string; use function method_exists; -/** - * HTML response - */ final class HtmlResponse extends Response { - /** - * Constructor of the class - * - * @param int $statusCode * @param mixed $html * * @throws InvalidArgumentException @@ -44,33 +31,27 @@ public function __construct(int $statusCode, $html) { parent::__construct($statusCode); - $this->setBody($this->createBody($html)); - + $this->setBody(self::createBody($html)); $this->setHeader('Content-Type', 'text/html; charset=utf-8'); } /** - * Creates the response body from the given HTML data - * * @param mixed $html * - * @return StreamInterface - * * @throws InvalidArgumentException */ - private function createBody($html): StreamInterface + private static function createBody($html): StreamInterface { if ($html instanceof StreamInterface) { return $html; } if (is_object($html) && method_exists($html, '__toString')) { - /** @var string */ - $html = $html->__toString(); + $html = (string) $html; } if (!is_string($html)) { - throw new InvalidArgumentException('Unable to create HTML response due to unexpected HTML data'); + throw new InvalidArgumentException('Unable to create the HTML response due to a unexpected HTML type'); } $stream = new PhpTempStream('r+b'); diff --git a/src/Response/JsonResponse.php b/src/Response/JsonResponse.php index 95c01f1..be0838b 100644 --- a/src/Response/JsonResponse.php +++ b/src/Response/JsonResponse.php @@ -11,38 +11,23 @@ namespace Sunrise\Http\Message\Response; -/** - * Import classes - */ +use JsonException; use Psr\Http\Message\StreamInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Response; use Sunrise\Http\Message\Stream\PhpTempStream; -use JsonException; -/** - * Import functions - */ use function json_encode; +use function sprintf; -/** - * Import constants - */ use const JSON_THROW_ON_ERROR; -/** - * JSON response - */ final class JsonResponse extends Response { - /** - * Constructor of the class - * - * @param int $statusCode * @param mixed $data - * @param int $flags * @param int<1, max> $depth + * @psalm-param int<1, 2147483647> $depth * * @throws InvalidArgumentException */ @@ -50,39 +35,34 @@ public function __construct(int $statusCode, $data, int $flags = 0, int $depth = { parent::__construct($statusCode); - $this->setBody($this->createBody($data, $flags, $depth)); - + $this->setBody(self::createBody($data, $flags, $depth)); $this->setHeader('Content-Type', 'application/json; charset=utf-8'); } /** - * Creates the response body from the given JSON data - * * @param mixed $data - * @param int $flags * @param int<1, max> $depth - * - * @return StreamInterface + * @psalm-param int<1, 2147483647> $depth * * @throws InvalidArgumentException */ - private function createBody($data, int $flags, int $depth): StreamInterface + private static function createBody($data, int $flags, int $depth): StreamInterface { if ($data instanceof StreamInterface) { return $data; } try { - $payload = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth); + $json = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth); } catch (JsonException $e) { throw new InvalidArgumentException(sprintf( - 'Unable to create JSON response due to invalid JSON data: %s', + 'Unable to create the JSON response due to an invalid data: %s', $e->getMessage() ), 0, $e); } $stream = new PhpTempStream('r+b'); - $stream->write($payload); + $stream->write($json); $stream->rewind(); return $stream; diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 88a8d6f..2109aa6 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -11,22 +11,13 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -/** - * HTTP Response Message Factory - * - * @link https://www.php-fig.org/psr/psr-17/ - */ class ResponseFactory implements ResponseFactoryInterface { - /** - * {@inheritdoc} + * @inheritDoc */ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface { diff --git a/src/ServerRequest.php b/src/ServerRequest.php index 4685dd5..07cbb0b 100644 --- a/src/ServerRequest.php +++ b/src/ServerRequest.php @@ -11,68 +11,44 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function array_key_exists; use function array_walk_recursive; use function is_array; use function is_object; -/** - * ServerRequest - * - * @link https://www.php-fig.org/psr/psr-7/ - */ class ServerRequest extends Request implements ServerRequestInterface { - /** - * The server parameters - * * @var array */ private array $serverParams; /** - * The request's query parameters - * * @var array */ private array $queryParams; /** - * The request's cookie parameters - * * @var array */ private array $cookieParams; /** - * The request's uploaded files - * * @var array */ private array $uploadedFiles = []; /** - * The request's parsed body - * * @var array|object|null */ private $parsedBody = null; /** - * The request attributes - * * @var array */ private array $attributes; @@ -80,11 +56,8 @@ class ServerRequest extends Request implements ServerRequestInterface /** * Constructor of the class * - * @param string|null $protocolVersion - * @param string|null $method * @param mixed $uri * @param array|null $headers - * @param StreamInterface|null $body * * @param array $serverParams * @param array $queryParams @@ -94,7 +67,6 @@ class ServerRequest extends Request implements ServerRequestInterface * @param array $attributes * * @throws InvalidArgumentException - * If one of the arguments isn't valid. */ public function __construct( ?string $protocolVersion = null, @@ -111,15 +83,15 @@ public function __construct( ) { parent::__construct($method, $uri, $headers, $body); - if (isset($protocolVersion)) { + if ($protocolVersion !== null) { $this->setProtocolVersion($protocolVersion); } - if (!empty($uploadedFiles)) { + if ($uploadedFiles !== []) { $this->setUploadedFiles($uploadedFiles); } - if (isset($parsedBody)) { + if ($parsedBody !== null) { $this->setParsedBody($parsedBody); } @@ -130,7 +102,7 @@ public function __construct( } /** - * Gets the server parameters + * {@inheritDoc} * * @return array */ @@ -140,7 +112,7 @@ public function getServerParams(): array } /** - * Gets the request's query parameters + * {@inheritDoc} * * @return array */ @@ -150,7 +122,7 @@ public function getQueryParams(): array } /** - * Creates a new instance of the request with the given query parameters + * {@inheritDoc} * * @param array $query * @@ -165,7 +137,7 @@ public function withQueryParams(array $query): ServerRequestInterface } /** - * Gets the request's cookie parameters + * {@inheritDoc} * * @return array */ @@ -175,7 +147,7 @@ public function getCookieParams(): array } /** - * Creates a new instance of the request with the given cookie parameters + * {@inheritDoc} * * @param array $cookies * @@ -190,7 +162,7 @@ public function withCookieParams(array $cookies): ServerRequestInterface } /** - * Gets the request's uploaded files + * {@inheritDoc} * * @return array */ @@ -200,14 +172,13 @@ public function getUploadedFiles(): array } /** - * Creates a new instance of the request with the given uploaded files + * {@inheritDoc} * * @param array $uploadedFiles * * @return static * * @throws InvalidArgumentException - * If one of the files isn't valid. */ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { @@ -218,7 +189,7 @@ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface } /** - * Gets the request's parsed body + * {@inheritDoc} * * @return array|object|null */ @@ -228,14 +199,13 @@ public function getParsedBody() } /** - * Creates a new instance of the request with the given parsed body + * {@inheritDoc} * * @param array|object|null $data * * @return static * * @throws InvalidArgumentException - * If the data isn't valid. */ public function withParsedBody($data): ServerRequestInterface { @@ -246,7 +216,7 @@ public function withParsedBody($data): ServerRequestInterface } /** - * Gets the request attributes + * {@inheritDoc} * * @return array */ @@ -256,9 +226,7 @@ public function getAttributes(): array } /** - * Gets the request's attribute value by the given name - * - * Returns the default value if the attribute wasn't found. + * {@inheritDoc} * * @param array-key $name * @param mixed $default @@ -275,7 +243,7 @@ public function getAttribute($name, $default = null) } /** - * Creates a new instance of the request with the given attribute + * {@inheritDoc} * * @param array-key $name * @param mixed $value @@ -291,7 +259,7 @@ public function withAttribute($name, $value): ServerRequestInterface } /** - * Creates a new instance of the request without an attribute with the given name + * {@inheritDoc} * * @param array-key $name * @@ -310,10 +278,7 @@ public function withoutAttribute($name): ServerRequestInterface * * @param array $files * - * @return void - * * @throws InvalidArgumentException - * If one of the files isn't valid. */ final protected function setUploadedFiles(array $files): void { @@ -327,10 +292,7 @@ final protected function setUploadedFiles(array $files): void * * @param array|object|null $data * - * @return void - * * @throws InvalidArgumentException - * If the parsed body isn't valid. */ final protected function setParsedBody($data): void { @@ -344,14 +306,11 @@ final protected function setParsedBody($data): void * * @param array $files * - * @return void - * * @throws InvalidArgumentException - * If one of the files isn't valid. */ private function validateUploadedFiles(array $files): void { - if ([] === $files) { + if ($files === []) { return; } @@ -359,7 +318,7 @@ private function validateUploadedFiles(array $files): void * @psalm-suppress MissingClosureParamType */ array_walk_recursive($files, static function ($file): void { - if (! ($file instanceof UploadedFileInterface)) { + if (!($file instanceof UploadedFileInterface)) { throw new InvalidArgumentException('Invalid uploaded file'); } }); @@ -370,19 +329,14 @@ private function validateUploadedFiles(array $files): void * * @param mixed $data * - * @return void - * * @throws InvalidArgumentException - * If the parsed body isn't valid. */ private function validateParsedBody($data): void { - if (null === $data) { + if ($data === null || is_array($data) || is_object($data)) { return; } - if (!is_array($data) && !is_object($data)) { - throw new InvalidArgumentException('Invalid parsed body'); - } + throw new InvalidArgumentException('Invalid parsed body'); } } diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index 5b9ed0f..6021daa 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -11,32 +11,19 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Sunrise\Http\Message\Stream\PhpInputStream; -/** - * ServerRequestFactory - * - * @link https://www.php-fig.org/psr/psr-17/ - */ class ServerRequestFactory implements ServerRequestFactoryInterface { - /** - * Creates a new request from superglobals variables - * * @param array|null $serverParams * @param array|null $queryParams * @param array|null $cookieParams * @param array|null $uploadedFiles * @param array|null $parsedBody * - * @return ServerRequestInterface - * * @link http://php.net/manual/en/language.variables.superglobals.php * @link https://www.php-fig.org/psr/psr-15/meta/ */ @@ -68,9 +55,8 @@ public static function fromGlobals( } /** - * Creates a new request + * {@inheritDoc} * - * @param string $method * @param mixed $uri * @param array $serverParams */ diff --git a/src/Stream.php b/src/Stream.php index 7a52a40..a424fc0 100644 --- a/src/Stream.php +++ b/src/Stream.php @@ -11,17 +11,11 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\StreamInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Exception\RuntimeException; use Throwable; -/** - * Import functions - */ use function fclose; use function feof; use function fread; @@ -34,41 +28,21 @@ use function stream_get_meta_data; use function strpbrk; -/** - * Import constants - */ use const SEEK_SET; -/** - * Stream - * - * @link https://www.php-fig.org/psr/psr-7/ - */ class Stream implements StreamInterface { - /** - * The stream resource - * * @var resource|null */ private $resource; - /** - * Signals to close the stream on destruction - * - * @var bool - */ private bool $autoClose; /** - * Constructor of the class - * * @param mixed $resource - * @param bool $autoClose * * @throws InvalidArgumentException - * If the stream cannot be initialized with the resource. */ public function __construct($resource, bool $autoClose = true) { @@ -81,14 +55,9 @@ public function __construct($resource, bool $autoClose = true) } /** - * Creates a stream - * * @param mixed $resource * - * @return StreamInterface - * * @throws InvalidArgumentException - * If the stream cannot be initialized with the resource. */ public static function create($resource): StreamInterface { @@ -99,9 +68,6 @@ public static function create($resource): StreamInterface return new self($resource); } - /** - * Destructor of the class - */ public function __destruct() { if ($this->autoClose) { @@ -110,11 +76,7 @@ public function __destruct() } /** - * Detaches a resource from the stream - * - * Returns NULL if the stream already without a resource. - * - * @return resource|null + * @inheritDoc */ public function detach() { @@ -125,11 +87,7 @@ public function detach() } /** - * Closes the stream - * - * @link http://php.net/manual/en/function.fclose.php - * - * @return void + * @inheritDoc */ public function close(): void { @@ -142,11 +100,7 @@ public function close(): void } /** - * Checks if the end of the stream is reached - * - * @link http://php.net/manual/en/function.feof.php - * - * @return bool + * @inheritDoc */ public function eof(): bool { @@ -158,13 +112,7 @@ public function eof(): bool } /** - * Gets the stream pointer position - * - * @link http://php.net/manual/en/function.ftell.php - * - * @return int - * - * @throws RuntimeException + * @inheritDoc */ public function tell(): int { @@ -181,9 +129,7 @@ public function tell(): int } /** - * Checks if the stream is seekable - * - * @return bool + * @inheritDoc */ public function isSeekable(): bool { @@ -198,11 +144,7 @@ public function isSeekable(): bool } /** - * Moves the stream pointer to the beginning - * - * @return void - * - * @throws RuntimeException + * @inheritDoc */ public function rewind(): void { @@ -210,16 +152,7 @@ public function rewind(): void } /** - * Moves the stream pointer to the given position - * - * @link http://php.net/manual/en/function.fseek.php - * - * @param int $offset - * @param int $whence - * - * @return void - * - * @throws RuntimeException + * @inheritDoc */ public function seek($offset, $whence = SEEK_SET): void { @@ -238,9 +171,7 @@ public function seek($offset, $whence = SEEK_SET): void } /** - * Checks if the stream is writable - * - * @return bool + * @inheritDoc */ public function isWritable(): bool { @@ -255,17 +186,7 @@ public function isWritable(): bool } /** - * Writes the given string to the stream - * - * Returns the number of bytes written to the stream. - * - * @link http://php.net/manual/en/function.fwrite.php - * - * @param string $string - * - * @return int - * - * @throws RuntimeException + * @inheritDoc */ public function write($string): int { @@ -286,9 +207,7 @@ public function write($string): int } /** - * Checks if the stream is readable - * - * @return bool + * @inheritDoc */ public function isReadable(): bool { @@ -303,17 +222,10 @@ public function isReadable(): bool } /** - * Reads the given number of bytes from the stream + * @inheritDoc * - * @link http://php.net/manual/en/function.fread.php - * - * @param int $length * @psalm-param int $length - * @phpstan-param int<0, max> $length - * - * @return string - * - * @throws RuntimeException + * @phpstan-param int<1, max> $length */ public function read($length): string { @@ -334,13 +246,7 @@ public function read($length): string } /** - * Reads the remainder of the stream - * - * @link http://php.net/manual/en/function.stream-get-contents.php - * - * @return string - * - * @throws RuntimeException + * @inheritDoc */ public function getContents(): string { @@ -361,13 +267,7 @@ public function getContents(): string } /** - * Gets the stream metadata - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * - * @param string|null $key - * - * @return mixed + * @inheritDoc */ public function getMetadata($key = null) { @@ -384,14 +284,7 @@ public function getMetadata($key = null) } /** - * Gets the stream size - * - * Returns NULL if the stream doesn't have a resource, - * or if the stream size cannot be determined. - * - * @link http://php.net/manual/en/function.fstat.php - * - * @return int|null + * @inheritDoc */ public function getSize(): ?int { @@ -409,11 +302,7 @@ public function getSize(): ?int } /** - * Converts the stream to a string - * - * @link http://php.net/manual/en/language.oop5.magic.php#object.tostring - * - * @return string + * @inheritDoc */ public function __toString(): string { diff --git a/src/Stream/FileStream.php b/src/Stream/FileStream.php index c3ecac1..8e3447e 100644 --- a/src/Stream/FileStream.php +++ b/src/Stream/FileStream.php @@ -11,36 +11,36 @@ namespace Sunrise\Http\Message\Stream; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Stream; +use Throwable; -/** - * Import functions - */ use function fopen; use function is_resource; use function sprintf; -/** - * FileStream - */ final class FileStream extends Stream { + /** + * @throws InvalidArgumentException + */ + public function __construct(string $filename, string $mode) + { + parent::__construct(self::openFile($filename, $mode)); + } /** - * Constructor of the class - * - * @param string $filename - * @param string $mode + * @return resource * * @throws InvalidArgumentException */ - public function __construct(string $filename, string $mode) + private static function openFile(string $filename, string $mode) { - $resource = @fopen($filename, $mode); + try { + $resource = @fopen($filename, $mode); + } catch (Throwable $e) { + $resource = false; + } if (!is_resource($resource)) { throw new InvalidArgumentException(sprintf( @@ -50,6 +50,6 @@ public function __construct(string $filename, string $mode) )); } - parent::__construct($resource); + return $resource; } } diff --git a/src/Stream/PhpInputStream.php b/src/Stream/PhpInputStream.php index 3f37f88..9083e8b 100644 --- a/src/Stream/PhpInputStream.php +++ b/src/Stream/PhpInputStream.php @@ -11,38 +11,34 @@ namespace Sunrise\Http\Message\Stream; -/** - * Import classes - */ use Sunrise\Http\Message\Stream; -/** - * Import functions - */ use function fopen; +use function fseek; use function stream_copy_to_stream; -/** - * @link https://www.php.net/manual/en/wrappers.php.php#wrappers.php.input - */ +use const SEEK_SET; + final class PhpInputStream extends Stream { + public function __construct() + { + parent::__construct(self::copyInput()); + } /** - * Constructor of the class + * @return resource */ - public function __construct() + private static function copyInput() { - /** @var resource */ + /** @var resource $input */ $input = fopen('php://input', 'rb'); + /** @var resource $resource */ + $resource = fopen('php://temp', 'r+b'); - /** @var resource */ - $handle = fopen('php://temp', 'r+b'); - - stream_copy_to_stream($input, $handle); - - parent::__construct($handle); + stream_copy_to_stream($input, $resource); + fseek($resource, 0, SEEK_SET); - $this->rewind(); + return $resource; } } diff --git a/src/Stream/PhpMemoryStream.php b/src/Stream/PhpMemoryStream.php index 67dc1a1..f8e87b7 100644 --- a/src/Stream/PhpMemoryStream.php +++ b/src/Stream/PhpMemoryStream.php @@ -11,27 +11,12 @@ namespace Sunrise\Http\Message\Stream; -/** - * Import classes - */ use Sunrise\Http\Message\Stream; -/** - * Import functions - */ use function fopen; -/** - * @link https://www.php.net/manual/en/wrappers.php.php#wrappers.php.memory - */ final class PhpMemoryStream extends Stream { - - /** - * Constructor of the class - * - * @param string $mode - */ public function __construct(string $mode = 'r+b') { parent::__construct(fopen('php://memory', $mode)); diff --git a/src/Stream/PhpTempStream.php b/src/Stream/PhpTempStream.php index 74dd59e..6e9f619 100644 --- a/src/Stream/PhpTempStream.php +++ b/src/Stream/PhpTempStream.php @@ -11,31 +11,20 @@ namespace Sunrise\Http\Message\Stream; -/** - * Import classes - */ use Sunrise\Http\Message\Stream; -/** - * Import functions - */ use function fopen; use function sprintf; -/** - * @link https://www.php.net/manual/en/wrappers.php.php#wrappers.php.memory - */ final class PhpTempStream extends Stream { - /** - * Constructor of the class - * - * @param string $mode * @param int<0, max> $maxmemory */ public function __construct(string $mode = 'r+b', int $maxmemory = 2097152) { - parent::__construct(fopen(sprintf('php://temp/maxmemory:%d', $maxmemory), $mode)); + $uri = sprintf('php://temp/maxmemory:%d', $maxmemory); + + parent::__construct(fopen($uri, $mode)); } } diff --git a/src/Stream/TempFileStream.php b/src/Stream/TempFileStream.php index 7de5bb4..3eed475 100644 --- a/src/Stream/TempFileStream.php +++ b/src/Stream/TempFileStream.php @@ -11,35 +11,31 @@ namespace Sunrise\Http\Message\Stream; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\RuntimeException; use Sunrise\Http\Message\Stream; -/** - * Import functions - */ use function fopen; use function is_resource; use function is_writable; use function sys_get_temp_dir; use function tempnam; -/** - * TempFileStream - */ final class TempFileStream extends Stream { + /** + * @throws RuntimeException + */ + public function __construct(string $prefix = '') + { + parent::__construct(self::createFile($prefix)); + } /** - * Constructor of the class - * - * @param string $prefix + * @return resource * * @throws RuntimeException */ - public function __construct(string $prefix = '') + private static function createFile(string $prefix) { $dirname = sys_get_temp_dir(); if (!is_writable($dirname)) { @@ -48,14 +44,14 @@ public function __construct(string $prefix = '') $filename = tempnam($dirname, $prefix); if ($filename === false) { - throw new RuntimeException('Temporary file name cannot be generated'); + throw new RuntimeException('Temporary file cannot be created'); } - $resource = fopen($filename, 'w+b'); + $resource = fopen($filename, 'r+b'); if (!is_resource($resource)) { - throw new RuntimeException('Temporary file cannot be created or opened'); + throw new RuntimeException('Temporary file cannot be opened'); } - parent::__construct($resource); + return $resource; } } diff --git a/src/Stream/TmpfileStream.php b/src/Stream/TmpfileStream.php index 51f331c..c255cdd 100644 --- a/src/Stream/TmpfileStream.php +++ b/src/Stream/TmpfileStream.php @@ -11,36 +11,35 @@ namespace Sunrise\Http\Message\Stream; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\RuntimeException; use Sunrise\Http\Message\Stream; -/** - * Import functions - */ use function is_resource; use function is_writable; use function sys_get_temp_dir; use function tmpfile; /** - * The tmpfile() function opens a unique temporary file in binary + * The {@see tmpfile()} function opens a unique temporary file in binary * read/write (w+b) mode. The file will be automatically deleted * when it is closed or the program terminates. - * - * @link https://www.php.net/tmpfile */ final class TmpfileStream extends Stream { + /** + * @throws RuntimeException + */ + public function __construct() + { + parent::__construct(self::createFile()); + } /** - * Constructor of the class + * @return resource * * @throws RuntimeException */ - public function __construct() + private static function createFile() { $dirname = sys_get_temp_dir(); if (!is_writable($dirname)) { @@ -52,6 +51,6 @@ public function __construct() throw new RuntimeException('Temporary file cannot be created or opened'); } - parent::__construct($resource); + return $resource; } } diff --git a/src/StreamFactory.php b/src/StreamFactory.php index bcfaf7d..ee6cc30 100644 --- a/src/StreamFactory.php +++ b/src/StreamFactory.php @@ -11,24 +11,15 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; use Sunrise\Http\Message\Stream\FileStream; use Sunrise\Http\Message\Stream\PhpTempStream; -/** - * StreamFactory - * - * @link https://www.php-fig.org/psr/psr-17/ - */ class StreamFactory implements StreamFactoryInterface { - /** - * {@inheritdoc} + * @inheritDoc */ public function createStream(string $content = ''): StreamInterface { @@ -44,7 +35,7 @@ public function createStream(string $content = ''): StreamInterface } /** - * {@inheritdoc} + * @inheritDoc */ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface { @@ -52,7 +43,7 @@ public function createStreamFromFile(string $filename, string $mode = 'r'): Stre } /** - * {@inheritdoc} + * @inheritDoc */ public function createStreamFromResource($resource): StreamInterface { diff --git a/src/UploadedFile.php b/src/UploadedFile.php index 7be2471..8adb5ea 100644 --- a/src/UploadedFile.php +++ b/src/UploadedFile.php @@ -11,28 +11,24 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; -use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Exception\RuntimeException; -use Sunrise\Http\Message\Stream\FileStream; +use Throwable; +use TypeError; -/** - * Import functions - */ use function dirname; +use function gettype; use function is_dir; use function is_file; +use function is_readable; +use function is_string; +use function is_uploaded_file; use function is_writable; +use function move_uploaded_file; +use function rename; use function sprintf; -use function unlink; -/** - * Import constants - */ use const UPLOAD_ERR_OK; use const UPLOAD_ERR_INI_SIZE; use const UPLOAD_ERR_FORM_SIZE; @@ -42,17 +38,9 @@ use const UPLOAD_ERR_CANT_WRITE; use const UPLOAD_ERR_EXTENSION; -/** - * UploadedFile - * - * @link https://www.php-fig.org/psr/psr-7/ - */ class UploadedFile implements UploadedFileInterface { - /** - * List of upload errors - * * @link https://www.php.net/manual/en/features.file-upload.errors.php * * @var array @@ -68,57 +56,14 @@ class UploadedFile implements UploadedFileInterface UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension', ]; - /** - * The file stream - * - * @var StreamInterface|null - */ - private ?StreamInterface $stream = null; - - /** - * The file size - * - * @var int|null - */ + private ?StreamInterface $stream; private ?int $size; - - /** - * The file's error code - * - * @var int - */ private int $errorCode; - - /** - * The file's error message - * - * @var string - */ private string $errorMessage; - - /** - * The client's file name - * - * @var string|null - */ private ?string $clientFilename; - - /** - * The client's file media type - * - * @var string|null - */ private ?string $clientMediaType; + private bool $isMoved = false; - /** - * Constructor of the class - * - * @param StreamInterface|null $stream - * @param int|null $size - * @param int $error - * @param string|null $clientFilename - * @param string|null $clientMediaType - */ public function __construct( ?StreamInterface $stream, ?int $size = null, @@ -126,121 +71,81 @@ public function __construct( ?string $clientFilename = null, ?string $clientMediaType = null ) { - // It doesn't make sense to keep the stream - // if the file wasn't successfully uploaded... - if (UPLOAD_ERR_OK === $error) { - $this->stream = $stream; - } - + $this->stream = $stream; $this->size = $size; $this->errorCode = $error; - $this->errorMessage = self::UPLOAD_ERRORS[$error] ?? 'Unknown error'; + $this->errorMessage = self::UPLOAD_ERRORS[$error] ?? 'Unknown file upload error'; $this->clientFilename = $clientFilename; $this->clientMediaType = $clientMediaType; } /** - * Gets the file stream - * - * @return StreamInterface - * - * @throws RuntimeException - * - If the file has no a stream due to an error; - * - If the file was already moved. + * @inheritDoc */ public function getStream(): StreamInterface { - if (UPLOAD_ERR_OK <> $this->errorCode) { - throw new RuntimeException(sprintf( - 'Uploaded file has no a stream due to the error #%d (%s)', - $this->errorCode, - $this->errorMessage - )); + if ($this->isMoved) { + throw new RuntimeException('Uploaded file was moved'); } - if (!isset($this->stream)) { - throw new RuntimeException( - 'Uploaded file has no a stream because it was already moved' - ); + if ($this->errorCode !== UPLOAD_ERR_OK) { + throw new RuntimeException($this->errorMessage, $this->errorCode); + } + + if ($this->stream === null) { + throw new RuntimeException('Uploaded file has no stream'); } return $this->stream; } /** - * Moves the file to the given path - * - * @param string $targetPath - * - * @return void - * - * @throws InvalidArgumentException - * If the target path cannot be used. - * - * @throws RuntimeException - * - If the file has no a stream due to an error; - * - If the file was already moved; - * - If the file cannot be read. + * @inheritDoc */ public function moveTo($targetPath): void { - if (UPLOAD_ERR_OK <> $this->errorCode) { - throw new RuntimeException(sprintf( - 'Uploaded file cannot be moved due to the error #%d (%s)', - $this->errorCode, - $this->errorMessage + /** @psalm-suppress TypeDoesNotContainType */ + if (!is_string($targetPath)) { + throw new TypeError(sprintf( + 'Argument #1 ($targetPath) must be of type string, %s given', + gettype($targetPath), )); } - if (!isset($this->stream)) { - throw new RuntimeException( - 'Uploaded file cannot be moved because it was already moved' - ); - } + $sourceStream = $this->getStream(); - if (!$this->stream->isReadable()) { - throw new RuntimeException( - 'Uploaded file cannot be moved because it is not readable' - ); + $sourcePath = $sourceStream->getMetadata('uri'); + if (!is_string($sourcePath) || !is_file($sourcePath) || !is_readable($sourcePath)) { + throw new RuntimeException('Uploaded file does not exist or is not readable'); } - try { - $targetStream = new FileStream($targetPath, 'wb'); - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf( - 'Uploaded file cannot be moved due to the error: %s', - $e->getMessage() - )); + $sourceDirname = dirname($sourcePath); + if (!is_writable($sourceDirname)) { + throw new RuntimeException('To move the uploaded file, the source directory must be writable'); } - if ($this->stream->isSeekable()) { - $this->stream->rewind(); + $targetDirname = dirname($targetPath); + if (!is_dir($targetDirname) || !is_writable($targetDirname)) { + throw new RuntimeException('To move the uploaded file, the target directory must exist and be writable'); } - while (!$this->stream->eof()) { - $targetStream->write($this->stream->read(4096)); + try { + $this->isMoved = is_uploaded_file($sourcePath) + ? move_uploaded_file($sourcePath, $targetPath) + : rename($sourcePath, $targetPath); + } catch (Throwable $e) { } - $targetStream->close(); - - /** @var string|null */ - $sourcePath = $this->stream->getMetadata('uri'); + if (!$this->isMoved) { + throw new RuntimeException('Failed to move the uploaded file'); + } - $this->stream->close(); + $sourceStream->close(); $this->stream = null; - - if (isset($sourcePath) && is_file($sourcePath)) { - $sourceDir = dirname($sourcePath); - if (is_writable($sourceDir)) { - unlink($sourcePath); - } - } } /** - * Gets the file size - * - * @return int|null + * @inheritDoc */ public function getSize(): ?int { @@ -248,9 +153,7 @@ public function getSize(): ?int } /** - * Gets the file's error code - * - * @return int + * @inheritDoc */ public function getError(): int { @@ -258,9 +161,7 @@ public function getError(): int } /** - * Gets the client's file name - * - * @return string|null + * @inheritDoc */ public function getClientFilename(): ?string { @@ -268,9 +169,7 @@ public function getClientFilename(): ?string } /** - * Gets the client's file media type - * - * @return string|null + * @inheritDoc */ public function getClientMediaType(): ?string { diff --git a/src/UploadedFileFactory.php b/src/UploadedFileFactory.php index 6deae8e..c401533 100644 --- a/src/UploadedFileFactory.php +++ b/src/UploadedFileFactory.php @@ -11,28 +11,16 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UploadedFileInterface; -/** - * Import constants - */ use const UPLOAD_ERR_OK; -/** - * UploadedFileFactory - * - * @link https://www.php-fig.org/psr/psr-17/ - */ class UploadedFileFactory implements UploadedFileFactoryInterface { - /** - * {@inheritdoc} + * @inheritDoc */ public function createUploadedFile( StreamInterface $stream, diff --git a/src/Uri.php b/src/Uri.php index f488230..a5f6357 100644 --- a/src/Uri.php +++ b/src/Uri.php @@ -11,9 +11,6 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\UriInterface; use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Uri\Component\Fragment; @@ -24,79 +21,23 @@ use Sunrise\Http\Message\Uri\Component\Scheme; use Sunrise\Http\Message\Uri\Component\UserInfo; -/** - * Import functions - */ use function is_string; use function ltrim; use function parse_url; use function strncmp; -/** - * Uniform Resource Identifier - * - * @link https://tools.ietf.org/html/rfc3986 - * @link https://www.php-fig.org/psr/psr-7/ - */ class Uri implements UriInterface { - - /** - * Scheme of the URI - * - * @var string - */ private string $scheme = ''; - - /** - * User Information of the URI - * - * @var string - */ private string $userInfo = ''; - - /** - * Host of the URI - * - * @var string - */ private string $host = ''; - - /** - * Port of the URI - * - * @var int|null - */ private ?int $port = null; - - /** - * Path of the URI - * - * @var string - */ private string $path = ''; - - /** - * Query of the URI - * - * @var string - */ private string $query = ''; - - /** - * Fragment of the URI - * - * @var string - */ private string $fragment = ''; /** - * Constructor of the class - * - * @param string $uri - * * @throws InvalidArgumentException - * If the URI isn't valid. */ public function __construct(string $uri = '') { @@ -104,52 +45,13 @@ public function __construct(string $uri = '') return; } - $components = parse_url($uri); - if ($components === false) { - throw new InvalidArgumentException('Invalid URI'); - } - - if (isset($components['scheme'])) { - $this->setScheme($components['scheme']); - } - - if (isset($components['user'])) { - $this->setUserInfo( - $components['user'], - $components['pass'] ?? null - ); - } - - if (isset($components['host'])) { - $this->setHost($components['host']); - } - - if (isset($components['port'])) { - $this->setPort($components['port']); - } - - if (isset($components['path'])) { - $this->setPath($components['path']); - } - - if (isset($components['query'])) { - $this->setQuery($components['query']); - } - - if (isset($components['fragment'])) { - $this->setFragment($components['fragment']); - } + $this->parseUri($uri); } /** - * Creates a URI - * * @param mixed $uri * - * @return UriInterface - * * @throws InvalidArgumentException - * If the URI isn't valid. */ public static function create($uri): UriInterface { @@ -165,10 +67,7 @@ public static function create($uri): UriInterface } /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - * If the scheme isn't valid. + * @inheritDoc */ public function withScheme($scheme): UriInterface { @@ -179,10 +78,9 @@ public function withScheme($scheme): UriInterface } /** - * {@inheritdoc} + * @inheritDoc * * @throws InvalidArgumentException - * If the user information isn't valid. */ public function withUserInfo($user, $password = null): UriInterface { @@ -193,10 +91,7 @@ public function withUserInfo($user, $password = null): UriInterface } /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - * If the host isn't valid. + * @inheritDoc */ public function withHost($host): UriInterface { @@ -207,10 +102,7 @@ public function withHost($host): UriInterface } /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - * If the port isn't valid. + * @inheritDoc */ public function withPort($port): UriInterface { @@ -221,10 +113,7 @@ public function withPort($port): UriInterface } /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - * If the path isn't valid. + * @inheritDoc */ public function withPath($path): UriInterface { @@ -235,10 +124,7 @@ public function withPath($path): UriInterface } /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - * If the query isn't valid. + * @inheritDoc */ public function withQuery($query): UriInterface { @@ -249,10 +135,9 @@ public function withQuery($query): UriInterface } /** - * {@inheritdoc} + * @inheritDoc * * @throws InvalidArgumentException - * If the fragment isn't valid. */ public function withFragment($fragment): UriInterface { @@ -263,7 +148,7 @@ public function withFragment($fragment): UriInterface } /** - * {@inheritdoc} + * @inheritDoc */ public function getScheme(): string { @@ -271,7 +156,7 @@ public function getScheme(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function getUserInfo(): string { @@ -279,7 +164,7 @@ public function getUserInfo(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function getHost(): string { @@ -287,7 +172,7 @@ public function getHost(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function getPort(): ?int { @@ -305,7 +190,7 @@ public function getPort(): ?int } /** - * {@inheritdoc} + * @inheritDoc */ public function getPath(): string { @@ -318,7 +203,7 @@ public function getPath(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function getQuery(): string { @@ -326,7 +211,7 @@ public function getQuery(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function getFragment(): string { @@ -334,7 +219,7 @@ public function getFragment(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function getAuthority(): string { @@ -357,7 +242,7 @@ public function getAuthority(): string } /** - * {@inheritdoc} + * @inheritDoc */ public function __toString(): string { @@ -411,14 +296,9 @@ public function __toString(): string } /** - * Sets the given scheme to the URI - * * @param mixed $scheme * - * @return void - * * @throws InvalidArgumentException - * If the scheme isn't valid. */ final protected function setScheme($scheme): void { @@ -426,15 +306,10 @@ final protected function setScheme($scheme): void } /** - * Sets the given user information to the URI - * * @param mixed $user * @param mixed $password * - * @return void - * * @throws InvalidArgumentException - * If the user information isn't valid. */ final protected function setUserInfo($user, $password): void { @@ -442,14 +317,9 @@ final protected function setUserInfo($user, $password): void } /** - * Sets the given host to the URI - * * @param mixed $host * - * @return void - * * @throws InvalidArgumentException - * If the host isn't valid. */ final protected function setHost($host): void { @@ -457,14 +327,9 @@ final protected function setHost($host): void } /** - * Sets the given port to the URI - * * @param mixed $port * - * @return void - * * @throws InvalidArgumentException - * If the port isn't valid. */ final protected function setPort($port): void { @@ -472,14 +337,9 @@ final protected function setPort($port): void } /** - * Sets the given path to the URI - * * @param mixed $path * - * @return void - * * @throws InvalidArgumentException - * If the path isn't valid. */ final protected function setPath($path): void { @@ -487,14 +347,9 @@ final protected function setPath($path): void } /** - * Sets the given query to the URI - * * @param mixed $query * - * @return void - * * @throws InvalidArgumentException - * If the query isn't valid. */ final protected function setQuery($query): void { @@ -502,17 +357,45 @@ final protected function setQuery($query): void } /** - * Sets the given fragment to the URI - * * @param mixed $fragment * - * @return void - * * @throws InvalidArgumentException - * If the fragment isn't valid. */ final protected function setFragment($fragment): void { $this->fragment = (new Fragment($fragment))->getValue(); } + + /** + * @throws InvalidArgumentException + */ + private function parseUri(string $uri): void + { + $components = parse_url($uri); + if ($components === false) { + throw new InvalidArgumentException('Invalid URI'); + } + + if (isset($components['scheme'])) { + $this->setScheme($components['scheme']); + } + if (isset($components['user'])) { + $this->setUserInfo($components['user'], $components['pass'] ?? null); + } + if (isset($components['host'])) { + $this->setHost($components['host']); + } + if (isset($components['port'])) { + $this->setPort($components['port']); + } + if (isset($components['path'])) { + $this->setPath($components['path']); + } + if (isset($components['query'])) { + $this->setQuery($components['query']); + } + if (isset($components['fragment'])) { + $this->setFragment($components['fragment']); + } + } } diff --git a/src/Uri/Component/ComponentInterface.php b/src/Uri/Component/ComponentInterface.php index 0060522..6ec43c3 100644 --- a/src/Uri/Component/ComponentInterface.php +++ b/src/Uri/Component/ComponentInterface.php @@ -11,15 +11,9 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * ComponentInterface - */ interface ComponentInterface { - /** - * Gets the component value - * * @return mixed */ public function getValue(); diff --git a/src/Uri/Component/Fragment.php b/src/Uri/Component/Fragment.php index 198d45b..8423334 100644 --- a/src/Uri/Component/Fragment.php +++ b/src/Uri/Component/Fragment.php @@ -11,48 +11,26 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_replace_callback; use function rawurlencode; /** - * URI component "fragment" - * * @link https://tools.ietf.org/html/rfc3986#section-3.5 */ final class Fragment implements ComponentInterface { - - /** - * Regular expression used for the component normalization - * - * @var string - */ // phpcs:ignore Generic.Files.LineLength private const NORMALIZATION_REGEX = '/(?:%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x3b\x3d\x3f-\x5a\x5f\x61-\x7a\x7e])*|(.?)/u'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { @@ -66,11 +44,11 @@ public function __construct($value) $this->value = (string) preg_replace_callback( self::NORMALIZATION_REGEX, - static function (array $match): string { - /** @var array{0: string, 1?: string} $match */ - return isset($match[1]) ? rawurlencode($match[1]) : $match[0]; - }, - $value + static fn(array $matches): string => ( + /** @var array{0: string, 1?: string} $matches */ + isset($matches[1]) ? rawurlencode($matches[1]) : $matches[0] + ), + $value, ); } diff --git a/src/Uri/Component/Host.php b/src/Uri/Component/Host.php index fefc041..c72da8d 100644 --- a/src/Uri/Component/Host.php +++ b/src/Uri/Component/Host.php @@ -11,49 +11,27 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_replace_callback; use function rawurlencode; use function strtolower; /** - * URI component "host" - * * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 */ final class Host implements ComponentInterface { - - /** - * Regular expression used for the component normalization - * - * @var string - */ // phpcs:ignore Generic.Files.LineLength private const NORMALIZATION_REGEX = '/(?:%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x2e\x30-\x39\x3b\x3d\x41-\x5a\x5f\x61-\x7a\x7e]+)|(.?)/u'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { @@ -67,11 +45,11 @@ public function __construct($value) $this->value = (string) preg_replace_callback( self::NORMALIZATION_REGEX, - static function (array $match): string { - /** @var array{0: string, 1?: string} $match */ - return isset($match[1]) ? rawurlencode($match[1]) : $match[0]; - }, - $value + static fn(array $matches): string => ( + /** @var array{0: string, 1?: string} $matches */ + isset($matches[1]) ? rawurlencode($matches[1]) : $matches[0] + ), + $value, ); // the component is case-insensitive... diff --git a/src/Uri/Component/Password.php b/src/Uri/Component/Password.php index c2461a0..0002fee 100644 --- a/src/Uri/Component/Password.php +++ b/src/Uri/Component/Password.php @@ -11,48 +11,26 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_replace_callback; use function rawurlencode; /** - * URI component "password" - * * @link https://tools.ietf.org/html/rfc3986#section-3.2.1 */ final class Password implements ComponentInterface { - - /** - * Regular expression used for the component normalization - * - * @var string - */ // phpcs:ignore Generic.Files.LineLength private const NORMALIZATION_REGEX = '/(?:%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x2e\x30-\x39\x3b\x3d\x41-\x5a\x5f\x61-\x7a\x7e]+)|(.?)/u'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { @@ -66,21 +44,17 @@ public function __construct($value) $this->value = (string) preg_replace_callback( self::NORMALIZATION_REGEX, - static function (array $match): string { - /** @var array{0: string, 1?: string} $match */ - return isset($match[1]) ? rawurlencode($match[1]) : $match[0]; - }, - $value + static fn(array $matches): string => ( + /** @var array{0: string, 1?: string} $matches */ + isset($matches[1]) ? rawurlencode($matches[1]) : $matches[0] + ), + $value, ); } /** - * Creates a password component - * * @param mixed $password * - * @return Password - * * @throws InvalidArgumentException */ public static function create($password): Password diff --git a/src/Uri/Component/Path.php b/src/Uri/Component/Path.php index dbcf91b..abdf3a6 100644 --- a/src/Uri/Component/Path.php +++ b/src/Uri/Component/Path.php @@ -11,48 +11,26 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_replace_callback; use function rawurlencode; /** - * URI component "path" - * * @link https://tools.ietf.org/html/rfc3986#section-3.3 */ final class Path implements ComponentInterface { - - /** - * Regular expression used for the component normalization - * - * @var string - */ // phpcs:ignore Generic.Files.LineLength private const NORMALIZATION_REGEX = '/(?:%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x3b\x3d\x40-\x5a\x5f\x61-\x7a\x7e]+)|(.?)/u'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { @@ -66,11 +44,11 @@ public function __construct($value) $this->value = (string) preg_replace_callback( self::NORMALIZATION_REGEX, - static function (array $match): string { - /** @var array{0: string, 1?: string} $match */ - return isset($match[1]) ? rawurlencode($match[1]) : $match[0]; - }, - $value + static fn(array $matches): string => ( + /** @var array{0: string, 1?: string} $matches */ + isset($matches[1]) ? rawurlencode($matches[1]) : $matches[0] + ), + $value, ); } diff --git a/src/Uri/Component/Port.php b/src/Uri/Component/Port.php index ec96d9d..97f7028 100644 --- a/src/Uri/Component/Port.php +++ b/src/Uri/Component/Port.php @@ -11,44 +11,27 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_int; /** - * URI component "port" - * * @link https://tools.ietf.org/html/rfc3986#section-3.2.3 */ final class Port implements ComponentInterface { + private const MIN_VALUE = 1; + private const MAX_VALUE = (2 ** 16) - 1; - /** - * The component value - * - * @var int|null - */ private ?int $value = null; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { - $min = 1; - $max = (2 ** 16) - 1; - if ($value === null) { return; } @@ -57,7 +40,7 @@ public function __construct($value) throw new InvalidArgumentException('URI component "port" must be an integer'); } - if (!($value >= $min && $value <= $max)) { + if (!($value >= self::MIN_VALUE && $value <= self::MAX_VALUE)) { throw new InvalidArgumentException('Invalid URI component "port"'); } diff --git a/src/Uri/Component/Query.php b/src/Uri/Component/Query.php index c7322a3..e89ed6d 100644 --- a/src/Uri/Component/Query.php +++ b/src/Uri/Component/Query.php @@ -11,48 +11,26 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_replace_callback; use function rawurlencode; /** - * URI component "query" - * * @link https://tools.ietf.org/html/rfc3986#section-3.4 */ final class Query implements ComponentInterface { - - /** - * Regular expression used for the component normalization - * - * @var string - */ // phpcs:ignore Generic.Files.LineLength private const NORMALIZATION_REGEX = '/(?:%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x3b\x3d\x3f-\x5a\x5f\x61-\x7a\x7e]+)|(.?)/u'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { @@ -66,11 +44,11 @@ public function __construct($value) $this->value = (string) preg_replace_callback( self::NORMALIZATION_REGEX, - static function (array $match): string { - /** @var array{0: string, 1?: string} $match */ - return isset($match[1]) ? rawurlencode($match[1]) : $match[0]; - }, - $value + static fn(array $matches): string => ( + /** @var array{0: string, 1?: string} $matches */ + isset($matches[1]) ? rawurlencode($matches[1]) : $matches[0] + ), + $value, ); } diff --git a/src/Uri/Component/Scheme.php b/src/Uri/Component/Scheme.php index abe3aaf..ce8bedf 100644 --- a/src/Uri/Component/Scheme.php +++ b/src/Uri/Component/Scheme.php @@ -11,47 +11,25 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_match; use function strtolower; /** - * URI component "scheme" - * * @link https://tools.ietf.org/html/rfc3986#section-3.1 */ final class Scheme implements ComponentInterface { - - /** - * Regular expression used for the component validation - * - * @var string - */ private const VALIDATION_REGEX = '/^(?:[A-Za-z][0-9A-Za-z\x2b\x2d\x2e]*)?$/'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { diff --git a/src/Uri/Component/User.php b/src/Uri/Component/User.php index 8daff17..2433857 100644 --- a/src/Uri/Component/User.php +++ b/src/Uri/Component/User.php @@ -11,48 +11,26 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; -/** - * Import functions - */ use function is_string; use function preg_replace_callback; use function rawurlencode; /** - * URI component "user" - * * @link https://tools.ietf.org/html/rfc3986#section-3.2.1 */ final class User implements ComponentInterface { - - /** - * Regular expression used for the component normalization - * - * @var string - */ // phpcs:ignore Generic.Files.LineLength private const NORMALIZATION_REGEX = '/(?:%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x2e\x30-\x39\x3b\x3d\x41-\x5a\x5f\x61-\x7a\x7e]+)|(.?)/u'; - /** - * The component value - * - * @var string - */ private string $value = ''; /** - * Constructor of the class - * * @param mixed $value * * @throws InvalidArgumentException - * If the component isn't valid. */ public function __construct($value) { @@ -66,21 +44,17 @@ public function __construct($value) $this->value = (string) preg_replace_callback( self::NORMALIZATION_REGEX, - static function (array $match): string { - /** @var array{0: string, 1?: string} $match */ - return isset($match[1]) ? rawurlencode($match[1]) : $match[0]; - }, - $value + static fn(array $matches): string => ( + /** @var array{0: string, 1?: string} $matches */ + isset($matches[1]) ? rawurlencode($matches[1]) : $matches[0] + ), + $value, ); } /** - * Creates a user component - * * @param mixed $user * - * @return User - * * @throws InvalidArgumentException */ public static function create($user): User diff --git a/src/Uri/Component/UserInfo.php b/src/Uri/Component/UserInfo.php index 0bc570a..a28a4f9 100644 --- a/src/Uri/Component/UserInfo.php +++ b/src/Uri/Component/UserInfo.php @@ -11,47 +11,27 @@ namespace Sunrise\Http\Message\Uri\Component; -/** - * Import classes - */ use Sunrise\Http\Message\Exception\InvalidArgumentException; /** - * URI component "User Information" - * * @link https://tools.ietf.org/html/rfc3986#section-3.2.1 */ final class UserInfo implements ComponentInterface { - - /** - * URI component "user" - * - * @var User - */ private User $user; - - /** - * URI component "password" - * - * @var Password|null - */ private ?Password $password = null; /** - * Constructor of the class - * * @param mixed $user * @param mixed $password * * @throws InvalidArgumentException - * If the user or password aren't valid. */ public function __construct($user, $password = null) { $this->user = User::create($user); - if (isset($password)) { + if ($password !== null) { $this->password = Password::create($password); } } @@ -65,7 +45,7 @@ public function getValue(): string { $value = $this->user->getValue(); - if (isset($this->password)) { + if ($this->password !== null) { $value .= ':' . $this->password->getValue(); } diff --git a/src/UriFactory.php b/src/UriFactory.php index f663d40..e8aa00c 100644 --- a/src/UriFactory.php +++ b/src/UriFactory.php @@ -11,22 +11,13 @@ namespace Sunrise\Http\Message; -/** - * Import classes - */ use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface; -/** - * UriFactory - * - * @link https://www.php-fig.org/psr/psr-17/ - */ class UriFactory implements UriFactoryInterface { - /** - * {@inheritdoc} + * @inheritDoc */ public function createUri(string $uri = ''): UriInterface { diff --git a/tests/BaseMessageTest.php b/tests/BaseMessageTest.php index af6d133..b1b0a01 100644 --- a/tests/BaseMessageTest.php +++ b/tests/BaseMessageTest.php @@ -186,7 +186,7 @@ public function testSetHeaderWithInvalidName(): void public function testSetHeaderWithValueAsEmptyArray(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo" HTTP header value cannot be an empty array'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo" cannot be an empty array'); $this->createSubject()->withHeader('X-Foo', []); } @@ -194,7 +194,7 @@ public function testSetHeaderWithValueAsEmptyArray(): void public function testSetHeaderWithValueAsNull(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" must be a string'); $this->createSubject()->withHeader('X-Foo', null); } @@ -202,7 +202,7 @@ public function testSetHeaderWithValueAsNull(): void public function testSetHeaderWithValueAsNullAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" must be a string'); $this->createSubject()->withHeader('X-Foo', ['bar', null, 'baz']); } @@ -210,7 +210,7 @@ public function testSetHeaderWithValueAsNullAmongOthers(): void public function testSetHeaderWithValueAsNumber(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" must be a string'); $this->createSubject()->withHeader('X-Foo', 42); } @@ -218,7 +218,7 @@ public function testSetHeaderWithValueAsNumber(): void public function testSetHeaderWithValueAsNumberAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" must be a string'); $this->createSubject()->withHeader('X-Foo', ['bar', 42, 'baz']); } @@ -226,7 +226,7 @@ public function testSetHeaderWithValueAsNumberAmongOthers(): void public function testSetHeaderWithInvalidValue(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" is invalid'); $this->createSubject()->withHeader('X-Foo', "\0"); } @@ -234,7 +234,7 @@ public function testSetHeaderWithInvalidValue(): void public function testSetHeaderWithInvalidValueAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" is invalid'); $this->createSubject()->withHeader('X-Foo', ['bar', "\0", 'baz']); } @@ -339,7 +339,7 @@ public function testAddHeaderWithInvalidName(): void public function testAddHeaderWithValueAsEmptyArray(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo" HTTP header value cannot be an empty array'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo" cannot be an empty array'); $this->createSubject()->withAddedHeader('X-Foo', []); } @@ -347,7 +347,7 @@ public function testAddHeaderWithValueAsEmptyArray(): void public function testAddHeaderWithValueAsNull(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" must be a string'); $this->createSubject()->withAddedHeader('X-Foo', null); } @@ -355,7 +355,7 @@ public function testAddHeaderWithValueAsNull(): void public function testAddHeaderWithValueAsNullAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" must be a string'); $this->createSubject()->withAddedHeader('X-Foo', ['bar', null, 'baz']); } @@ -363,7 +363,7 @@ public function testAddHeaderWithValueAsNullAmongOthers(): void public function testAddHeaderWithValueAsNumber(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" must be a string'); $this->createSubject()->withAddedHeader('X-Foo', 42); } @@ -371,7 +371,7 @@ public function testAddHeaderWithValueAsNumber(): void public function testAddHeaderWithValueAsNumberAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" must be a string'); $this->createSubject()->withAddedHeader('X-Foo', ['bar', 42, 'baz']); } @@ -379,7 +379,7 @@ public function testAddHeaderWithValueAsNumberAmongOthers(): void public function testAddHeaderWithInvalidValue(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" is invalid'); $this->createSubject()->withAddedHeader('X-Foo', "\0"); } @@ -387,7 +387,7 @@ public function testAddHeaderWithInvalidValue(): void public function testAddHeaderWithInvalidValueAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" is invalid'); $this->createSubject()->withAddedHeader('X-Foo', ['bar', "\0", 'baz']); } @@ -776,7 +776,7 @@ public function testConstructorWithHeadersWithInvalidName(): void public function testConstructorWithHeadersWithValueAsEmptyArray(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo" HTTP header value cannot be an empty array'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo" cannot be an empty array'); $subject = $this->createSubjectWithHeaders(['X-Foo' => []]); @@ -788,7 +788,7 @@ public function testConstructorWithHeadersWithValueAsEmptyArray(): void public function testConstructorWithHeadersWithValueAsNull(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" must be a string'); $subject = $this->createSubjectWithHeaders(['X-Foo' => null]); @@ -800,7 +800,7 @@ public function testConstructorWithHeadersWithValueAsNull(): void public function testConstructorWithHeadersWithValueAsNullAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" must be a string'); $subject = $this->createSubjectWithHeaders(['X-Foo' => ['bar', null, 'baz']]); @@ -812,7 +812,7 @@ public function testConstructorWithHeadersWithValueAsNullAmongOthers(): void public function testConstructorWithHeadersWithValueAsNumber(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" must be a string'); $subject = $this->createSubjectWithHeaders(['X-Foo' => 42]); @@ -824,7 +824,7 @@ public function testConstructorWithHeadersWithValueAsNumber(): void public function testConstructorWithHeadersWithValueAsNumberAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value must be a string'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" must be a string'); $subject = $this->createSubjectWithHeaders(['X-Foo' => ['bar', 42, 'baz']]); @@ -836,7 +836,7 @@ public function testConstructorWithHeadersWithValueAsNumberAmongOthers(): void public function testConstructorWithHeadersWithInvalidValue(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" is invalid'); $subject = $this->createSubjectWithHeaders(['X-Foo' => "\0"]); @@ -848,7 +848,7 @@ public function testConstructorWithHeadersWithInvalidValue(): void public function testConstructorWithHeadersWithInvalidValueAmongOthers(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[1]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[1]" is invalid'); $subject = $this->createSubjectWithHeaders(['X-Foo' => ['bar', "\0", 'baz']]); diff --git a/tests/Request/JsonRequestTest.php b/tests/Request/JsonRequestTest.php new file mode 100644 index 0000000..69e4491 --- /dev/null +++ b/tests/Request/JsonRequestTest.php @@ -0,0 +1,54 @@ +assertSame('POST', $request->getMethod()); + $this->assertSame('/', (string) $request->getUri()); + $this->assertSame('application/json; charset=utf-8', $request->getHeaderLine('Content-Type')); + $this->assertStringStartsWith('php://temp', $request->getBody()->getMetadata('uri')); + $this->assertTrue($request->getBody()->isReadable()); + $this->assertTrue($request->getBody()->isWritable()); + $this->assertSame('["foo"]', $request->getBody()->__toString()); + } + + public function testConstructorWithJsonFlags(): void + { + $request = new JsonRequest('POST', '/', [], JSON_FORCE_OBJECT); + + $this->assertSame('{}', $request->getBody()->__toString()); + } + + public function testConstructorWithInvalidJson(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Unable to create the JSON request due to an invalid data: ' . + 'Maximum stack depth exceeded' + ); + + new JsonRequest('POST', '/', [], 0, 0); + } + + public function testConstructorWithStream(): void + { + $body = $this->createMock(StreamInterface::class); + $request = new JsonRequest('POST', '/', $body); + + $this->assertSame($body, $request->getBody()); + } +} diff --git a/tests/Response/HtmlResponseTest.php b/tests/Response/HtmlResponseTest.php index 89cced5..0bc52b4 100644 --- a/tests/Response/HtmlResponseTest.php +++ b/tests/Response/HtmlResponseTest.php @@ -39,12 +39,12 @@ public function __toString(): string public function testConstructorWithUnexpectedHtml(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unable to create HTML response due to unexpected HTML data'); + $this->expectExceptionMessage('Unable to create the HTML response due to a unexpected HTML type'); new HtmlResponse(200, null); } - public function testConstructorWithStreamBody(): void + public function testConstructorWithStream(): void { $html = $this->createMock(StreamInterface::class); $response = new HtmlResponse(200, $html); diff --git a/tests/Response/JsonResponseTest.php b/tests/Response/JsonResponseTest.php index 3d2dc9e..2b42dea 100644 --- a/tests/Response/JsonResponseTest.php +++ b/tests/Response/JsonResponseTest.php @@ -36,14 +36,14 @@ public function testConstructorWithInvalidJson(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'Unable to create JSON response due to invalid JSON data: ' . + 'Unable to create the JSON response due to an invalid data: ' . 'Maximum stack depth exceeded' ); new JsonResponse(200, [], 0, 0); } - public function testConstructorWithStreamBody(): void + public function testConstructorWithStream(): void { $body = $this->createMock(StreamInterface::class); $response = new JsonResponse(200, $body); diff --git a/tests/ServerRequestFactoryTest.php b/tests/ServerRequestFactoryTest.php index ad89982..bf9a7fe 100644 --- a/tests/ServerRequestFactoryTest.php +++ b/tests/ServerRequestFactoryTest.php @@ -121,7 +121,7 @@ public function testCreateServerRequestWithServerParamsWithHeaders( public function testCreateServerRequestWithServerParamsWithInvalidHeader(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" is invalid'); (new ServerRequestFactory)->createServerRequest('GET', new Uri(), ['HTTP_X_FOO' => "\0"]); } @@ -297,7 +297,7 @@ public function testCreateServerRequestFromGlobalsWithInvalidHeader(): void $_SERVER = ['HTTP_X_FOO' => "\0"]; $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "X-Foo[0]" HTTP header value is invalid'); + $this->expectExceptionMessage('The value of the HTTP header "X-Foo[0]" is invalid'); ServerRequestFactory::fromGlobals(); } diff --git a/tests/StreamTest.php b/tests/StreamTest.php index f3f968b..2826430 100644 --- a/tests/StreamTest.php +++ b/tests/StreamTest.php @@ -15,6 +15,7 @@ use function is_resource; use function stream_get_meta_data; +use const PHP_VERSION_ID; use const STDIN; use const STDOUT; @@ -51,7 +52,7 @@ public function testCreateWithResource(): void $this->assertSame($this->testResource, Stream::create($this->testResource)->detach()); } - public function testCreateWithUnexpectedOperand(): void + public function testCreateWithInvalidArgument(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Unexpected stream resource'); @@ -274,6 +275,10 @@ public function testSeekInUnseekableResource(): void public function testFailedSeek(): void { + if (PHP_VERSION_ID >= 80300) { + $this->markTestSkipped('Not relevant for this version of the PHP.'); + } + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Unable to move the stream pointer position'); diff --git a/tests/UploadedFileTest.php b/tests/UploadedFileTest.php index 1a3adc4..3dad6c3 100644 --- a/tests/UploadedFileTest.php +++ b/tests/UploadedFileTest.php @@ -6,13 +6,12 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\UploadedFileInterface; -use Sunrise\Http\Message\Exception\InvalidArgumentException; use Sunrise\Http\Message\Exception\RuntimeException; -use Sunrise\Http\Message\Stream\FileStream; use Sunrise\Http\Message\Stream\PhpTempStream; use Sunrise\Http\Message\Stream\TempFileStream; use Sunrise\Http\Message\Stream\TmpfileStream; use Sunrise\Http\Message\UploadedFile; +use TypeError; use const UPLOAD_ERR_CANT_WRITE; use const UPLOAD_ERR_EXTENSION; @@ -27,7 +26,7 @@ class UploadedFileTest extends TestCase { public function testContracts(): void { - $file = new UploadedFile(new PhpTempStream()); + $file = new UploadedFile(null); $this->assertInstanceOf(UploadedFileInterface::class, $file); } @@ -56,36 +55,40 @@ public function testConstructorWithRequiredParametersOnly(): void $this->assertNull($file->getClientMediaType()); } + public function testGetStreamWithoutStream(): void + { + $file = new UploadedFile(null); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Uploaded file has no stream'); + + $file->getStream(); + } + /** * @dataProvider uploadErrorCodeProvider */ - public function testGetsStreamWithError(int $errorCode): void + public function testGetStreamWithError(int $errorCode): void { - $file = new UploadedFile(new PhpTempStream(), null, $errorCode); + $file = new UploadedFile(null, null, $errorCode); - $errorMessage = UploadedFile::UPLOAD_ERRORS[$errorCode] ?? 'Unknown error'; + $expectedMessage = UploadedFile::UPLOAD_ERRORS[$errorCode] ?? 'Unknown file upload error'; $this->expectException(RuntimeException::class); - $this->expectExceptionMessage(sprintf( - 'Uploaded file has no a stream due to the error #%d (%s)', - $errorCode, - $errorMessage - )); + $this->expectExceptionCode($errorCode); + $this->expectExceptionMessage($expectedMessage); $file->getStream(); } - public function testGetsStreamAfterMove(): void + public function testGetStreamAfterMove(): void { $tmpfile = new TmpfileStream(); - - $file = new UploadedFile(new PhpTempStream()); + $file = new UploadedFile(new TmpfileStream()); $file->moveTo($tmpfile->getMetadata('uri')); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'Uploaded file has no a stream because it was already moved' - ); + $this->expectExceptionMessage('Uploaded file was moved'); $file->getStream(); } @@ -108,65 +111,72 @@ public function testMove(): void $this->assertFileDoesNotExist($srcPath); } + public function testMoveWithoutStream(): void + { + $file = new UploadedFile(null); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Uploaded file has no stream'); + + $file->moveTo('/'); + } + /** * @dataProvider uploadErrorCodeProvider */ public function testMoveWithError(int $errorCode): void { - $file = new UploadedFile(new PhpTempStream(), null, $errorCode); + $file = new UploadedFile(null, null, $errorCode); - $errorMessage = UploadedFile::UPLOAD_ERRORS[$errorCode] ?? 'Unknown error'; + $expectedMessage = UploadedFile::UPLOAD_ERRORS[$errorCode] ?? 'Unknown file upload error'; $this->expectException(RuntimeException::class); - $this->expectExceptionMessage(sprintf( - 'Uploaded file cannot be moved due to the error #%d (%s)', - $errorCode, - $errorMessage - )); + $this->expectExceptionCode($errorCode); + $this->expectExceptionMessage($expectedMessage); - $file->moveTo('/foo'); + $file->moveTo('/'); } public function testMoveAfterMove(): void { $tmpfile = new TmpfileStream(); - $file = new UploadedFile(new PhpTempStream()); + $file = new UploadedFile(new TmpfileStream()); $file->moveTo($tmpfile->getMetadata('uri')); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'Uploaded file cannot be moved because it was already moved' - ); + $this->expectExceptionMessage('Uploaded file was moved'); - $file->moveTo('/foo'); + $file->moveTo($tmpfile->getMetadata('uri')); } - public function testMoveUnreadableFile(): void + public function testMoveInvalidTargetPath(): void { - $tmpfile = new TmpfileStream(); + $file = new UploadedFile(null); + + $this->expectException(TypeError::class); - $file = new UploadedFile(new FileStream($tmpfile->getMetadata('uri'), 'w')); + $file->moveTo(null); + } + + public function testMoveNonFileStream(): void + { + $file = new UploadedFile(new PhpTempStream()); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'Uploaded file cannot be moved because it is not readable' - ); + $this->expectExceptionMessage('Uploaded file does not exist or is not readable'); - $file->moveTo('/foo'); + $file->moveTo('/'); } - public function testMoveUnwritableDirectory(): void + public function testCloseStreamAfterMove(): void { - $file = new UploadedFile(new PhpTempStream()); + $tmpfile = new TmpfileStream(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'Uploaded file cannot be moved due to the error: ' . - 'Unable to open the file "/4c32dad5-181f-46b7-a86a-15568e11fdf9/foo" in the mode "wb"' - ); + $file = new UploadedFile($tmpfile); + $file->moveTo($tmpfile->getMetadata('uri')); - $file->moveTo('/4c32dad5-181f-46b7-a86a-15568e11fdf9/foo'); + $this->assertNull($tmpfile->detach()); } public function uploadErrorCodeProvider(): array From ca8c382a463244da8e5d0d18070dee9a596a558b Mon Sep 17 00:00:00 2001 From: Anatoly Nekhay Date: Wed, 30 Oct 2024 02:14:28 +0100 Subject: [PATCH 2/4] v3.1 --- .scrutinizer.yml | 10 ++----- README.md | 71 ++++++++++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 0d791fe..ed0219b 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -3,25 +3,19 @@ build: nodes: analysis: environment: - php: 8.3 + php: 8.2 tests: override: - php-scrutinizer-run coverage: environment: - php: 8.3 + php: 8.2 tests: override: - command: XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-clover coverage.xml coverage: file: coverage.xml format: clover - php82: - environment: - php: 8.2 - tests: - override: - - command: php vendor/bin/phpunit php81: environment: php: 8.1 diff --git a/README.md b/README.md index 83d468e..6ed7b71 100644 --- a/README.md +++ b/README.md @@ -15,26 +15,27 @@ composer require sunrise/http-message ``` -## Documentation navigation +## Documentation Navigation -- [Server request from global environment](#server-request-from-global-environment) -- [HTML and JSON responses](#html-and-json-responses) -- - [HTML response](#html-response) -- - [JSON response](#json-response) +- [Server Request from Global Environment](#server-request-from-global-environment) +- [JSON and HTML Messages](#json-and-html-messages) +- - [JSON Request](#json-request) +- - [JSON Response](#json-response) +- - [HTML Response](#html-response) - [Streams](#streams) -- - [File stream](#file-stream) -- - [PHP input stream](#php-input-stream) -- - [PHP memory stream](#php-memory-stream) -- - [PHP temporary stream](#php-temporary-stream) -- - [Temporary file stream](#temporary-file-stream) +- - [File Stream](#file-stream) +- - [PHP Input Stream](#php-input-stream) +- - [PHP Memory Stream](#php-memory-stream) +- - [PHP Temporary Stream](#php-temporary-stream) +- - [Temporary File Stream](#temporary-file-stream) - [PSR-7 and PSR-17](#psr-7-and-psr-17) - [Exceptions](#exceptions) -## How to use +## How to Use We highly recommend that you study [PSR-7](https://www.php-fig.org/psr/psr-7/) and [PSR-17](https://www.php-fig.org/psr/psr-17/) because only superficial examples will be presented below. -### Server request from global environment +### Server Request from Global Environment ```php use Sunrise\Http\Message\ServerRequestFactory; @@ -42,19 +43,25 @@ use Sunrise\Http\Message\ServerRequestFactory; $request = ServerRequestFactory::fromGlobals(); ``` -### HTML and JSON responses +### JSON and HTML Messages -#### HTML response +#### JSON Request ```php -use Sunrise\Http\Message\Response\HtmlResponse; +use Sunrise\Http\Message\Request\JsonRequest; -/** @var $html string|Stringable */ +/** @var $data mixed */ -$response = new HtmlResponse(200, $html); +$request = new JsonRequest('GET', '/', $data); +``` + +You can also specify [encoding flags](https://www.php.net/manual/en/json.constants.php#constant.json-hex-tag) and maximum nesting depth like below: + +```php +$request = new JsonRequest('GET', '/', $data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE, 512); ``` -#### JSON response +#### JSON Response ```php use Sunrise\Http\Message\Response\JsonResponse; @@ -70,9 +77,19 @@ You can also specify [encoding flags](https://www.php.net/manual/en/json.constan $response = new JsonResponse(200, $data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE, 512); ``` +#### HTML Response + +```php +use Sunrise\Http\Message\Response\HtmlResponse; + +/** @var $html string|Stringable */ + +$response = new HtmlResponse(200, $html); +``` + ### Streams -#### File stream +#### File Stream ```php use Sunrise\Http\Message\Stream\FileStream; @@ -80,7 +97,7 @@ use Sunrise\Http\Message\Stream\FileStream; $fileStream = new FileStream('/folder/file', 'r+b'); ``` -#### PHP input stream +#### PHP Input Stream More details about the stream at the [official page](https://www.php.net/manual/en/wrappers.php.php#wrappers.php.input). @@ -90,7 +107,7 @@ use Sunrise\Http\Message\Stream\PhpInputStream; $inputStream = new PhpInputStream(); ``` -#### PHP memory stream +#### PHP Memory Stream More details about the stream at the [official page](https://www.php.net/manual/en/wrappers.php.php#wrappers.php.memory). @@ -100,7 +117,7 @@ use Sunrise\Http\Message\Stream\PhpMemoryStream; $memoryStream = new PhpMemoryStream('r+b'); ``` -#### PHP temporary stream +#### PHP Temporary Stream More details about the stream at the [official page](https://www.php.net/manual/en/wrappers.php.php#wrappers.php.memory). @@ -110,7 +127,7 @@ use Sunrise\Http\Message\Stream\PhpTempStream; $tempStream = new PhpTempStream('r+b'); ``` -You can also specify the memory limit, when the limit is reached, PHP will start using the temporary file instead of memory. +You can also specify the memory limit; when the limit is reached, PHP will start using the temporary file instead of memory. > Please note that the default memory limit is 2MB. @@ -120,7 +137,7 @@ $maxMemory = 1e+6; // 1MB $tempStream = new PhpTempStream('r+b', $maxMemory); ``` -#### Temporary file stream +#### Temporary File Stream More details about the temporary file behaviour at [the official page](https://www.php.net/manual/en/function.tmpfile). @@ -168,7 +185,7 @@ The following classes implement PSR-17: ### Exceptions -Any exceptions of this package can be caught through the interface: +Any exceptions from this package can be caught through the interface: ```php use Sunrise\Http\Message\Exception\ExceptionInterface; @@ -182,13 +199,13 @@ try { --- -## Test run +## Test Run ```bash composer test ``` -## Useful links +## Useful Links - https://tools.ietf.org/html/rfc7230 - https://www.php-fig.org/psr/psr-7/ From fa195a7d60d298208d206b32465fcf05847ec259 Mon Sep 17 00:00:00 2001 From: Anatoly Nekhay Date: Wed, 30 Oct 2024 03:07:57 +0100 Subject: [PATCH 3/4] v3.1 --- README.md | 29 +++++++-- src/Request/UrlEncodedRequest.php | 81 +++++++++++++++++++++++++ tests/Request/UrlEncodedRequestTest.php | 54 +++++++++++++++++ 3 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 src/Request/UrlEncodedRequest.php create mode 100644 tests/Request/UrlEncodedRequestTest.php diff --git a/README.md b/README.md index 6ed7b71..5407ff7 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,7 @@ composer require sunrise/http-message ## Documentation Navigation - [Server Request from Global Environment](#server-request-from-global-environment) -- [JSON and HTML Messages](#json-and-html-messages) -- - [JSON Request](#json-request) -- - [JSON Response](#json-response) -- - [HTML Response](#html-response) +- [Typed Messages](#typed-messages) - [Streams](#streams) - - [File Stream](#file-stream) - - [PHP Input Stream](#php-input-stream) @@ -43,7 +40,7 @@ use Sunrise\Http\Message\ServerRequestFactory; $request = ServerRequestFactory::fromGlobals(); ``` -### JSON and HTML Messages +### Typed Messages #### JSON Request @@ -61,6 +58,28 @@ You can also specify [encoding flags](https://www.php.net/manual/en/json.constan $request = new JsonRequest('GET', '/', $data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE, 512); ``` +#### URL Encoded Request + +```php +use Sunrise\Http\Message\Request\UrlEncodedRequest; + +/** @var $data mixed */ + +$request = new UrlEncodedRequest('GET', '/', $data); +``` + +You can also specify [encoding type](https://www.php.net/manual/ru/url.constants.php#constant.php-query-rfc1738) like below: + +```php +use Sunrise\Http\Message\Request\UrlEncodedRequest; + +$encodingType = UrlEncodedRequest::ENCODING_TYPE_RFC1738; +// or +$encodingType = UrlEncodedRequest::ENCODING_TYPE_RFC3986; + +$request = new UrlEncodedRequest('GET', '/', $data, $encodingType); +``` + #### JSON Response ```php diff --git a/src/Request/UrlEncodedRequest.php b/src/Request/UrlEncodedRequest.php new file mode 100644 index 0000000..b54d8c1 --- /dev/null +++ b/src/Request/UrlEncodedRequest.php @@ -0,0 +1,81 @@ + + * @copyright Copyright (c) 2018, Anatoly Nekhay + * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE + * @link https://github.com/sunrise-php/http-message + */ + +namespace Sunrise\Http\Message\Request; + +use Psr\Http\Message\StreamInterface; +use Sunrise\Http\Message\Exception\InvalidArgumentException; +use Sunrise\Http\Message\Request; +use Sunrise\Http\Message\Stream\PhpTempStream; +use TypeError; + +use function gettype; +use function http_build_query; +use function is_array; +use function is_object; +use function sprintf; + +use const PHP_QUERY_RFC1738; +use const PHP_QUERY_RFC3986; + +/** + * @since 3.1.0 + */ +final class UrlEncodedRequest extends Request +{ + public const ENCODING_TYPE_RFC1738 = PHP_QUERY_RFC1738; + public const ENCODING_TYPE_RFC3986 = PHP_QUERY_RFC3986; + + /** + * @param mixed $uri + * @param array|object $data + * @param self::ENCODING_TYPE_* $encodingType + * + * @throws InvalidArgumentException + */ + public function __construct(string $method, $uri, $data, int $encodingType = self::ENCODING_TYPE_RFC1738) + { + /** + * @psalm-suppress DocblockTypeContradiction + * @phpstan-ignore-next-line + */ + if (!is_array($data) && !is_object($data)) { + throw new TypeError(sprintf( + 'Argument #3 ($data) must be of type string, %s given', + gettype($data), + )); + } + + parent::__construct($method, $uri); + + $this->setBody(self::createBody($data, $encodingType)); + $this->setHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); + } + + /** + * @param array|object $data + * @param self::ENCODING_TYPE_* $encodingType + */ + private static function createBody($data, int $encodingType): StreamInterface + { + if ($data instanceof StreamInterface) { + return $data; + } + + $encodedData = http_build_query($data, '', null, $encodingType); + + $stream = new PhpTempStream('r+b'); + $stream->write($encodedData); + $stream->rewind(); + + return $stream; + } +} diff --git a/tests/Request/UrlEncodedRequestTest.php b/tests/Request/UrlEncodedRequestTest.php new file mode 100644 index 0000000..50e6c54 --- /dev/null +++ b/tests/Request/UrlEncodedRequestTest.php @@ -0,0 +1,54 @@ + 'bar']); + + $this->assertSame('POST', $request->getMethod()); + $this->assertSame('/', (string) $request->getUri()); + $this->assertSame('application/x-www-form-urlencoded; charset=utf-8', $request->getHeaderLine('Content-Type')); + $this->assertStringStartsWith('php://temp', $request->getBody()->getMetadata('uri')); + $this->assertTrue($request->getBody()->isReadable()); + $this->assertTrue($request->getBody()->isWritable()); + $this->assertSame('foo=bar', $request->getBody()->__toString()); + } + + public function testConstructorWithObject(): void + { + $request = new UrlEncodedRequest('POST', '/', (object) ['foo' => 'bar']); + + $this->assertSame('foo=bar', $request->getBody()->__toString()); + } + + public function testConstructorWithDefaultEncodingType(): void + { + $request = new UrlEncodedRequest('POST', '/', ['foo' => 'bar baz']); + + $this->assertSame('foo=bar+baz', $request->getBody()->__toString()); + } + + public function testConstructorWithEncodingType(): void + { + $request = new UrlEncodedRequest('POST', '/', ['foo' => 'bar baz'], UrlEncodedRequest::ENCODING_TYPE_RFC3986); + + $this->assertSame('foo=bar%20baz', $request->getBody()->__toString()); + } + + public function testConstructorWithStream(): void + { + $body = $this->createMock(StreamInterface::class); + $request = new UrlEncodedRequest('POST', '/', $body); + + $this->assertSame($body, $request->getBody()); + } +} From 62fa8bad9547c6bb4177960eb7b07ccc3ad99d12 Mon Sep 17 00:00:00 2001 From: Anatoly Nekhay Date: Wed, 30 Oct 2024 03:19:31 +0100 Subject: [PATCH 4/4] v3.1 --- src/Request/UrlEncodedRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Request/UrlEncodedRequest.php b/src/Request/UrlEncodedRequest.php index b54d8c1..b89496a 100644 --- a/src/Request/UrlEncodedRequest.php +++ b/src/Request/UrlEncodedRequest.php @@ -70,7 +70,7 @@ private static function createBody($data, int $encodingType): StreamInterface return $data; } - $encodedData = http_build_query($data, '', null, $encodingType); + $encodedData = http_build_query($data, '', '', $encodingType); $stream = new PhpTempStream('r+b'); $stream->write($encodedData);