From c5aaf2f09d7947034fcb226242ed70e58268f045 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 30 Apr 2023 20:02:57 +0300 Subject: [PATCH 1/3] Psalm 1 + Improve `HeaderValueHelper` methods annotations --- CHANGELOG.md | 11 ++++---- psalm.xml | 5 +++- src/HeaderValueHelper.php | 54 +++++++++++++++++++++++++++++---------- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 491d492..982f10a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,21 @@ # Yii HTTP Change Log - ## 1.2.1 under development -- no changes in this release. +- Enh #45: Improve `HeaderValueHelper` methods annotations (@vjik) ## 1.2.0 November 09, 2021 -- New #26: Add `HeaderValueHelper` that has static methods to parse header value parameters (devanych) +- New #26: Add `HeaderValueHelper` that has static methods to parse header value parameters (@devanych) ## 1.1.1 February 10, 2021 -- Chg: Update yiisoft/strings dependency (samdark) +- Chg: Update yiisoft/strings dependency (@samdark) ## 1.1.0 December 28, 2020 -- Enh #12: Add `Method::ALL` and deprecated `Method::ANY` (samdark) -- Enh #20: Add `ContentDispositionHeader` that generate `Content-Disposition` header name and value (vjik) +- Enh #12: Add `Method::ALL` and deprecated `Method::ANY` (@samdark) +- Enh #20: Add `ContentDispositionHeader` that generate `Content-Disposition` header name and value (@vjik) ## 1.0.0 September 1, 2020 diff --git a/psalm.xml b/psalm.xml index 56b1b43..d091d59 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ + + + diff --git a/src/HeaderValueHelper.php b/src/HeaderValueHelper.php index 07feb13..4f463c5 100644 --- a/src/HeaderValueHelper.php +++ b/src/HeaderValueHelper.php @@ -11,7 +11,6 @@ use function count; use function explode; use function implode; -use function is_array; use function is_string; use function mb_strtolower; use function mb_strpos; @@ -29,7 +28,9 @@ use function usort; /** - * HeaderValueHelper parses the header value parameters. + * `HeaderValueHelper` parses the header value parameters. + * + * @psalm-type QFactorHeader = array{q: float}&non-empty-array */ final class HeaderValueHelper { @@ -70,7 +71,7 @@ final class HeaderValueHelper * @param bool $lowerCaseParameter Whether should cast header parameter name to lowercase. * @param bool $lowerCaseParameterValue Whether should cast header parameter value to lowercase. * - * @return array First element is the value, and key-value are the parameters. + * @return string[] First element is the value, and key-value are the parameters. */ public static function getValueAndParameters( string $headerValue, @@ -104,7 +105,9 @@ public static function getValueAndParameters( * @param bool $lowerCaseParameter Whether should cast header parameter name to lowercase. * @param bool $lowerCaseParameterValue Whether should cast header parameter value to lowercase. * - * @return array Key-value are the parameters. + * @return string[] Key-value are the parameters. + * + * @psalm-return array */ public static function getParameters( string $headerValueParameters, @@ -142,6 +145,7 @@ static function (array $matches) use (&$output, $lowerCaseParameter, $lowerCaseP return; } + /** @psalm-suppress MixedArrayAssignment False-positive error */ $output[$key] = $lowerCaseParameterValue ? mb_strtolower($value) : $value; }, $headerValueParameters, @@ -153,6 +157,7 @@ static function (array $matches) use (&$output, $lowerCaseParameter, $lowerCaseP throw new InvalidArgumentException('Invalid input: ' . $headerValueParameters); } } while ($headerValueParameters !== ''); + /** @var array $output */ return $output; } @@ -160,16 +165,19 @@ static function (array $matches) use (&$output, $lowerCaseParameter, $lowerCaseP /** * Returns a header value as "q" factor sorted list. * - * @param mixed $values Header value as a comma-separated string or already exploded string array. + * @link https://developer.mozilla.org/en-US/docs/Glossary/Quality_values + * @link https://www.ietf.org/rfc/rfc2045.html#section-2 + * @see getValueAndParameters + * + * @param string|string[] $values Header value as a comma-separated string or already exploded string array. * @param bool $lowerCaseValue Whether should cast header value to lowercase. * @param bool $lowerCaseParameter Whether should cast header parameter name to lowercase. * @param bool $lowerCaseParameterValue Whether should cast header parameter value to lowercase. * - * @return array The q factor sorted list. + * @return array[] The q factor sorted list. * - * @link https://developer.mozilla.org/en-US/docs/Glossary/Quality_values - * @link https://www.ietf.org/rfc/rfc2045.html#section-2 - * @see getValueAndParameters + * @psalm-return list + * @psalm-suppress MoreSpecificReturnType, LessSpecificReturnStatement Need for Psalm 4.30 */ public static function getSortedValueAndParameters( $values, @@ -177,17 +185,21 @@ public static function getSortedValueAndParameters( bool $lowerCaseParameter = true, bool $lowerCaseParameterValue = true ): array { - if (!is_array($values) && !is_string($values)) { - throw new InvalidArgumentException('Values are neither array nor string.'); - } + $values = (array) $values; $list = []; + foreach ($values as $headerValue) { + /** @psalm-suppress DocblockTypeContradiction Don't trust to annotations. */ + if (!is_string($headerValue)) { + throw new InvalidArgumentException('Values must be string or array of strings.'); + } - foreach ((array) $values as $headerValue) { /** @psalm-suppress InvalidOperand Presume that `preg_split` never returns false here. */ $list = [...$list, ...preg_split('/\s*,\s*/', trim($headerValue), -1, PREG_SPLIT_NO_EMPTY)]; } + /** @var string[] $list */ + if (count($list) === 0) { return []; } @@ -195,7 +207,12 @@ public static function getSortedValueAndParameters( $output = []; foreach ($list as $value) { - $parse = self::getValueAndParameters($value, $lowerCaseValue, $lowerCaseParameter, $lowerCaseParameterValue); + $parse = self::getValueAndParameters( + $value, + $lowerCaseValue, + $lowerCaseParameter, + $lowerCaseParameterValue + ); // case-insensitive "q" parameter $q = $parse['q'] ?? $parse['Q'] ?? 1.0; @@ -237,12 +254,20 @@ public static function getSortedAcceptTypes($values): array $output = self::getSortedValueAndParameters($values); usort($output, static function (array $a, array $b) { + /** + * @psalm-var QFactorHeader $a + * @psalm-var QFactorHeader $b + */ + if ($a['q'] !== $b['q']) { // The higher q value wins return $a['q'] > $b['q'] ? -1 : 1; } + /** @var string $typeA */ $typeA = reset($a); + + /** @var string $typeB */ $typeB = reset($b); if (strpos($typeA, '*') === false && strpos($typeB, '*') === false) { @@ -285,6 +310,7 @@ public static function getSortedAcceptTypes($values): array asort($value, SORT_STRING); $output[$key] = $type . ';' . implode(';', $value); } + /** @var string[] $output */ return $output; } From 3b899fca174165c0f546fb8508bc59d327f24e89 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 3 May 2023 14:04:11 +0300 Subject: [PATCH 2/3] Add tests --- CHANGELOG.md | 2 +- src/HeaderValueHelper.php | 9 +++++++-- tests/HeaderValueHelperTest.php | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 982f10a..daeff5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 1.2.1 under development -- Enh #45: Improve `HeaderValueHelper` methods annotations (@vjik) +- Enh #45: Improve `HeaderValueHelper` methods' annotations (@vjik) ## 1.2.0 November 09, 2021 diff --git a/src/HeaderValueHelper.php b/src/HeaderValueHelper.php index 4f463c5..e958318 100644 --- a/src/HeaderValueHelper.php +++ b/src/HeaderValueHelper.php @@ -11,6 +11,7 @@ use function count; use function explode; use function implode; +use function is_array; use function is_string; use function mb_strtolower; use function mb_strpos; @@ -185,13 +186,17 @@ public static function getSortedValueAndParameters( bool $lowerCaseParameter = true, bool $lowerCaseParameterValue = true ): array { + /** @var mixed $values Don't trust to annotations. */ + + if (!is_array($values) && !is_string($values)) { + throw new InvalidArgumentException('Values are neither array nor string.'); + } $values = (array) $values; $list = []; foreach ($values as $headerValue) { - /** @psalm-suppress DocblockTypeContradiction Don't trust to annotations. */ if (!is_string($headerValue)) { - throw new InvalidArgumentException('Values must be string or array of strings.'); + throw new InvalidArgumentException('Values must be array of strings.'); } /** @psalm-suppress InvalidOperand Presume that `preg_split` never returns false here. */ diff --git a/tests/HeaderValueHelperTest.php b/tests/HeaderValueHelperTest.php index e04a3d6..c78a207 100644 --- a/tests/HeaderValueHelperTest.php +++ b/tests/HeaderValueHelperTest.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use stdClass; use Yiisoft\Http\HeaderValueHelper; final class HeaderValueHelperTest extends TestCase @@ -65,6 +66,29 @@ public function testSortedValuesAndParameters($input, array $expected): void $this->assertSame($expected, HeaderValueHelper::getSortedValueAndParameters($input)); } + public function dataSortedValuesAndParametersInvalidValue(): array + { + $notArrayAndNotString = 'Values are neither array nor string.'; + $notArrayOfStrings = 'Values must be array of strings.'; + + return [ + 'object' => [new stdClass(), $notArrayAndNotString], + 'int' => [7, $notArrayAndNotString], + 'bool' => [true, $notArrayAndNotString], + 'array-of-int' => [[1, 2, 3], $notArrayOfStrings], + ]; + } + + /** + * @dataProvider dataSortedValuesAndParametersInvalidValue + */ + public function testSortedValuesAndParametersInvalidValue($value, string $message): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($message); + HeaderValueHelper::getSortedValueAndParameters($value); + } + public function qFactorSortFailDataProvider(): array { return [ From 3699492e8df0f2e6889f7ba77a89e1a13ed52109 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 3 May 2023 14:05:24 +0300 Subject: [PATCH 3/3] minor refactoring --- src/HeaderValueHelper.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/HeaderValueHelper.php b/src/HeaderValueHelper.php index e958318..e4057a2 100644 --- a/src/HeaderValueHelper.php +++ b/src/HeaderValueHelper.php @@ -191,10 +191,9 @@ public static function getSortedValueAndParameters( if (!is_array($values) && !is_string($values)) { throw new InvalidArgumentException('Values are neither array nor string.'); } - $values = (array) $values; $list = []; - foreach ($values as $headerValue) { + foreach ((array) $values as $headerValue) { if (!is_string($headerValue)) { throw new InvalidArgumentException('Values must be array of strings.'); }