From 38ef514a6c21335f29d9be64b097d2582ecbf8e4 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 10 Sep 2023 17:11:14 +0100 Subject: [PATCH] Various fixes (#582) --- README.md | 2 +- phpstan-baseline.neon | 71 ------------------------------------ psalm-baseline.xml | 30 +++++++-------- src/FnStream.php | 33 +++++++++-------- src/Header.php | 2 +- src/MessageTrait.php | 2 +- src/MultipartStream.php | 20 +++++++--- src/PumpStream.php | 6 +-- src/Request.php | 2 +- src/Response.php | 2 +- src/ServerRequest.php | 2 +- src/StreamDecoratorTrait.php | 2 +- src/StreamWrapper.php | 32 +++++++++++++++- src/UploadedFile.php | 4 +- src/Uri.php | 10 +++-- src/UriNormalizer.php | 4 +- src/Utils.php | 6 +-- tests/UtilsTest.php | 5 +++ 18 files changed, 104 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 4c7cbaa5..850fa9d7 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ class EofCallbackStream implements StreamInterface // Invoke the callback when EOF is hit. if ($this->eof()) { - call_user_func($this->callback); + ($this->callback)(); } return $result; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c159f369..37ed2ebd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -90,71 +90,11 @@ parameters: count: 1 path: src/FnStream.php - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:__toString\\(\\) should return string but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:detach\\(\\) should return resource\\|null but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:eof\\(\\) should return bool but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:getContents\\(\\) should return string but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:getSize\\(\\) should return int\\|null but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:isReadable\\(\\) should return bool but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:isSeekable\\(\\) should return bool but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:isWritable\\(\\) should return bool but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:read\\(\\) should return string but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:tell\\(\\) should return int but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\FnStream\\:\\:write\\(\\) should return int but returns mixed\\.$#" - count: 1 - path: src/FnStream.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 path: src/FnStream.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/Header.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 @@ -190,11 +130,6 @@ parameters: count: 1 path: src/Message.php - - - message: "#^Method GuzzleHttp\\\\Psr7\\\\MultipartStream\\:\\:getHeader\\(\\) has no return type specified\\.$#" - count: 1 - path: src/MultipartStream.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 @@ -295,11 +230,6 @@ parameters: count: 1 path: src/Utils.php - - - message: "#^Parameter \\#1 \\$keys of static method GuzzleHttp\\\\Psr7\\\\Utils\\:\\:caselessRemove\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: src/Utils.php - - message: "#^Parameter \\#1 \\$source of class GuzzleHttp\\\\Psr7\\\\PumpStream constructor expects callable\\(int\\)\\: \\(string\\|false\\|null\\), Closure\\(\\)\\: mixed given\\.$#" count: 1 @@ -319,4 +249,3 @@ parameters: message: "#^Variable \\$handle might not be defined\\.$#" count: 1 path: src/Utils.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 37b37e8b..aa7d70cf 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2,21 +2,21 @@ - _fn___toString)]]> - _fn_close)]]> - _fn_detach)]]> - _fn_eof)]]> - _fn_getContents)]]> - _fn_getMetadata, $key)]]> - _fn_getSize)]]> - _fn_isReadable)]]> - _fn_isSeekable)]]> - _fn_isWritable)]]> - _fn_read, $length)]]> - _fn_rewind)]]> - _fn_seek, $offset, $whence)]]> - _fn_tell)]]> - _fn_write, $string)]]> + _fn___toString)()]]> + _fn_close)()]]> + _fn_detach)()]]> + _fn_eof)()]]> + _fn_getContents)()]]> + _fn_getMetadata)($key)]]> + _fn_getSize)()]]> + _fn_isReadable)()]]> + _fn_isSeekable)()]]> + _fn_isWritable)()]]> + _fn_read)($length)]]> + _fn_rewind)()]]> + _fn_seek)($offset, $whence)]]> + _fn_tell)()]]> + _fn_write)($string)]]> diff --git a/src/FnStream.php b/src/FnStream.php index 9fdddb9c..9e6a7f31 100644 --- a/src/FnStream.php +++ b/src/FnStream.php @@ -54,7 +54,7 @@ public function __get(string $name): void public function __destruct() { if (isset($this->_fn_close)) { - call_user_func($this->_fn_close); + ($this->_fn_close)(); } } @@ -93,7 +93,8 @@ public static function decorate(StreamInterface $stream, array $methods) public function __toString(): string { try { - return call_user_func($this->_fn___toString); + /** @var string */ + return ($this->_fn___toString)(); } catch (\Throwable $e) { if (\PHP_VERSION_ID >= 70400) { throw $e; @@ -106,67 +107,67 @@ public function __toString(): string public function close(): void { - call_user_func($this->_fn_close); + ($this->_fn_close)(); } public function detach() { - return call_user_func($this->_fn_detach); + return ($this->_fn_detach)(); } public function getSize(): ?int { - return call_user_func($this->_fn_getSize); + return ($this->_fn_getSize)(); } public function tell(): int { - return call_user_func($this->_fn_tell); + return ($this->_fn_tell)(); } public function eof(): bool { - return call_user_func($this->_fn_eof); + return ($this->_fn_eof)(); } public function isSeekable(): bool { - return call_user_func($this->_fn_isSeekable); + return ($this->_fn_isSeekable)(); } public function rewind(): void { - call_user_func($this->_fn_rewind); + ($this->_fn_rewind)(); } public function seek($offset, $whence = SEEK_SET): void { - call_user_func($this->_fn_seek, $offset, $whence); + ($this->_fn_seek)($offset, $whence); } public function isWritable(): bool { - return call_user_func($this->_fn_isWritable); + return ($this->_fn_isWritable)(); } public function write($string): int { - return call_user_func($this->_fn_write, $string); + return ($this->_fn_write)($string); } public function isReadable(): bool { - return call_user_func($this->_fn_isReadable); + return ($this->_fn_isReadable)(); } public function read($length): string { - return call_user_func($this->_fn_read, $length); + return ($this->_fn_read)($length); } public function getContents(): string { - return call_user_func($this->_fn_getContents); + return ($this->_fn_getContents)(); } /** @@ -174,6 +175,6 @@ public function getContents(): string */ public function getMetadata($key = null) { - return call_user_func($this->_fn_getMetadata, $key); + return ($this->_fn_getMetadata)($key); } } diff --git a/src/Header.php b/src/Header.php index 6e38e003..bbce8b03 100644 --- a/src/Header.php +++ b/src/Header.php @@ -22,7 +22,7 @@ public static function parse($header): array foreach ((array) $header as $value) { foreach (self::splitList($value) as $val) { $part = []; - foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) ?: [] as $kvp) { if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { $m = $matches[0]; if (isset($m[1])) { diff --git a/src/MessageTrait.php b/src/MessageTrait.php index e0dd0d0f..65dbc4ba 100644 --- a/src/MessageTrait.php +++ b/src/MessageTrait.php @@ -141,7 +141,7 @@ public function withBody(StreamInterface $body): MessageInterface } /** - * @param array $headers + * @param (string|string[])[] $headers */ private function setHeaders(array $headers): void { diff --git a/src/MultipartStream.php b/src/MultipartStream.php index 41c48eef..d23fba8a 100644 --- a/src/MultipartStream.php +++ b/src/MultipartStream.php @@ -51,7 +51,7 @@ public function isWritable(): bool /** * Get the headers needed before transferring the content of a POST file * - * @param array $headers + * @param string[] $headers */ private function getHeaders(array $headers): string { @@ -112,10 +112,15 @@ private function addElement(AppendStream $stream, array $element): void $stream->addStream(Utils::streamFor("\r\n")); } + /** + * @param string[] $headers + * + * @return array{0: StreamInterface, 1: string[]} + */ private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array { // Set a default content-disposition header if one was no provided - $disposition = $this->getHeader($headers, 'content-disposition'); + $disposition = self::getHeader($headers, 'content-disposition'); if (!$disposition) { $headers['Content-Disposition'] = ($filename === '0' || $filename) ? sprintf( @@ -127,7 +132,7 @@ private function createElement(string $name, StreamInterface $stream, ?string $f } // Set a default content-length header if one was no provided - $length = $this->getHeader($headers, 'content-length'); + $length = self::getHeader($headers, 'content-length'); if (!$length) { if ($length = $stream->getSize()) { $headers['Content-Length'] = (string) $length; @@ -135,7 +140,7 @@ private function createElement(string $name, StreamInterface $stream, ?string $f } // Set a default Content-Type if one was not supplied - $type = $this->getHeader($headers, 'content-type'); + $type = self::getHeader($headers, 'content-type'); if (!$type && ($filename === '0' || $filename)) { $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream'; } @@ -143,11 +148,14 @@ private function createElement(string $name, StreamInterface $stream, ?string $f return [$stream, $headers]; } - private function getHeader(array $headers, string $key) + /** + * @param string[] $headers + */ + private static function getHeader(array $headers, string $key): ?string { $lowercaseHeader = strtolower($key); foreach ($headers as $k => $v) { - if (strtolower($k) === $lowercaseHeader) { + if (strtolower((string) $k) === $lowercaseHeader) { return $v; } } diff --git a/src/PumpStream.php b/src/PumpStream.php index 5585190c..e2040709 100644 --- a/src/PumpStream.php +++ b/src/PumpStream.php @@ -18,7 +18,7 @@ */ final class PumpStream implements StreamInterface { - /** @var callable|null */ + /** @var callable(int): (string|false|null)|null */ private $source; /** @var int|null */ @@ -163,9 +163,9 @@ public function getMetadata($key = null) private function pump(int $length): void { - if ($this->source) { + if ($this->source !== null) { do { - $data = call_user_func($this->source, $length); + $data = ($this->source)($length); if ($data === false || $data === null) { $this->source = null; diff --git a/src/Request.php b/src/Request.php index 7b39cae1..faafe1ad 100644 --- a/src/Request.php +++ b/src/Request.php @@ -28,7 +28,7 @@ class Request implements RequestInterface /** * @param string $method HTTP method * @param string|UriInterface $uri URI - * @param array $headers Request headers + * @param (string|string[])[] $headers Request headers * @param string|resource|StreamInterface|null $body Request body * @param string $version Protocol version */ diff --git a/src/Response.php b/src/Response.php index 8fc11478..00f16e2d 100644 --- a/src/Response.php +++ b/src/Response.php @@ -86,7 +86,7 @@ class Response implements ResponseInterface /** * @param int $status Status code - * @param array $headers Response headers + * @param (string|string[])[] $headers Response headers * @param string|resource|StreamInterface|null $body Response body * @param string $version Protocol version * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) diff --git a/src/ServerRequest.php b/src/ServerRequest.php index c852d96f..3cc95345 100644 --- a/src/ServerRequest.php +++ b/src/ServerRequest.php @@ -59,7 +59,7 @@ class ServerRequest extends Request implements ServerRequestInterface /** * @param string $method HTTP method * @param string|UriInterface $uri URI - * @param array $headers Request headers + * @param (string|string[])[] $headers Request headers * @param string|resource|StreamInterface|null $body Request body * @param string $version Protocol version * @param array $serverParams Typically the $_SERVER superglobal diff --git a/src/StreamDecoratorTrait.php b/src/StreamDecoratorTrait.php index 96196a3e..601c13af 100644 --- a/src/StreamDecoratorTrait.php +++ b/src/StreamDecoratorTrait.php @@ -70,7 +70,7 @@ public function __call(string $method, array $args) { /** @var callable $callable */ $callable = [$this->stream, $method]; - $result = call_user_func_array($callable, $args); + $result = ($callable)(...$args); // Always return the wrapped object if the result is a return $this return $result === $this->stream ? $this : $result; diff --git a/src/StreamWrapper.php b/src/StreamWrapper.php index b3655cb3..ae853881 100644 --- a/src/StreamWrapper.php +++ b/src/StreamWrapper.php @@ -122,7 +122,21 @@ public function stream_cast(int $cast_as) } /** - * @return array + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * } */ public function stream_stat(): array { @@ -152,7 +166,21 @@ public function stream_stat(): array } /** - * @return array + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * } */ public function url_stat(string $path, int $flags): array { diff --git a/src/UploadedFile.php b/src/UploadedFile.php index b1521bcf..b2671993 100644 --- a/src/UploadedFile.php +++ b/src/UploadedFile.php @@ -113,7 +113,7 @@ private function setError(int $error): void $this->error = $error; } - private function isStringNotEmpty($param): bool + private static function isStringNotEmpty($param): bool { return is_string($param) && false === empty($param); } @@ -163,7 +163,7 @@ public function moveTo($targetPath): void { $this->validateActive(); - if (false === $this->isStringNotEmpty($targetPath)) { + if (false === self::isStringNotEmpty($targetPath)) { throw new InvalidArgumentException( 'Invalid path provided for move operation; must be a non-empty string' ); diff --git a/src/Uri.php b/src/Uri.php index 553f2993..f1feee87 100644 --- a/src/Uri.php +++ b/src/Uri.php @@ -336,8 +336,8 @@ public static function withQueryValue(UriInterface $uri, string $key, ?string $v * * It has the same behavior as withQueryValue() but for an associative array of key => value. * - * @param UriInterface $uri URI to use as a base. - * @param array $keyValueArray Associative array of key and values + * @param UriInterface $uri URI to use as a base. + * @param (string|null)[] $keyValueArray Associative array of key and values */ public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface { @@ -638,7 +638,7 @@ private function filterPort($port): ?int } /** - * @param string[] $keys + * @param (string|int)[] $keys * * @return string[] */ @@ -650,7 +650,9 @@ private static function getFilteredQueryString(UriInterface $uri, array $keys): return []; } - $decodedKeys = array_map('rawurldecode', $keys); + $decodedKeys = array_map(function ($k): string { + return rawurldecode((string) $k); + }, $keys); return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); diff --git a/src/UriNormalizer.php b/src/UriNormalizer.php index 08ceaab3..e1745573 100644 --- a/src/UriNormalizer.php +++ b/src/UriNormalizer.php @@ -185,7 +185,7 @@ private static function capitalizePercentEncoding(UriInterface $uri): UriInterfa { $regex = '/(?:%[A-Fa-f0-9]{2})++/'; - $callback = function (array $match) { + $callback = function (array $match): string { return strtoupper($match[0]); }; @@ -201,7 +201,7 @@ private static function decodeUnreservedCharacters(UriInterface $uri): UriInterf { $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; - $callback = function (array $match) { + $callback = function (array $match): string { return rawurldecode($match[0]); }; diff --git a/src/Utils.php b/src/Utils.php index 917c05e3..bf5ea9db 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -14,18 +14,18 @@ final class Utils /** * Remove the items given by the keys, case insensitively from the data. * - * @param string[] $keys + * @param (string|int)[] $keys */ public static function caselessRemove(array $keys, array $data): array { $result = []; foreach ($keys as &$key) { - $key = strtolower($key); + $key = strtolower((string) $key); } foreach ($data as $k => $v) { - if (!is_string($k) || !in_array(strtolower($k), $keys)) { + if (!in_array(strtolower((string) $k), $keys)) { $result[$k] = $v; } } diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index 2f220726..c8a4bcdf 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -522,6 +522,11 @@ public function providesCaselessRemoveCases(): array ['Foo-Bar' => 'hello', 123 => '', 'Foo-BAR' => 'hello123', 'foobar' => 'baz'], [123 => '', 'foobar' => 'baz'], ], + [ + ['foo-Bar', 123], + ['Foo-Bar' => 'hello', 123 => '', 'Foo-BAR' => 'hello123', 'foobar' => 'baz'], + ['foobar' => 'baz'], + ], ]; }