diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9ac292fdc6..3519835579 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2884,7 +2884,13 @@ parameters: path: src/Controllers/Server/Status/StatusController.php - - message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|string, mixed given\.$#' + message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|numeric\-string, float\|int\|string given\.$#' + identifier: argument.type + count: 4 + path: src/Controllers/Server/Status/StatusController.php + + - + message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|numeric\-string, mixed given\.$#' identifier: argument.type count: 5 path: src/Controllers/Server/Status/StatusController.php @@ -6058,7 +6064,7 @@ parameters: path: src/Engines/Innodb.php - - message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|string, mixed given\.$#' + message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|numeric\-string, mixed given\.$#' identifier: argument.type count: 11 path: src/Engines/Innodb.php @@ -13888,7 +13894,7 @@ parameters: path: src/StorageEngine.php - - message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|string, mixed given\.$#' + message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|numeric\-string, mixed given\.$#' identifier: argument.type count: 1 path: src/StorageEngine.php @@ -15012,18 +15018,6 @@ parameters: count: 1 path: src/Util.php - - - message: '#^Loose comparison via "\!\=" is not allowed\.$#' - identifier: notEqual.notAllowed - count: 2 - path: src/Util.php - - - - message: '#^Loose comparison via "\=\=" is not allowed\.$#' - identifier: equal.notAllowed - count: 3 - path: src/Util.php - - message: '#^Method PhpMyAdmin\\Util\:\:getDbInfo\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -16888,7 +16882,7 @@ parameters: path: tests/unit/Controllers/Server/Status/QueriesControllerTest.php - - message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|string, mixed given\.$#' + message: '#^Parameter \#1 \$value of static method PhpMyAdmin\\Util\:\:formatNumber\(\) expects float\|int\|numeric\-string, mixed given\.$#' identifier: argument.type count: 1 path: tests/unit/Controllers/Server/Status/QueriesControllerTest.php @@ -19140,12 +19134,6 @@ parameters: count: 4 path: tests/unit/UtilTest.php - - - message: '#^Casting to string something that''s already string\.$#' - identifier: cast.useless - count: 1 - path: tests/unit/UtilTest.php - - message: '#^Parameter \#2 \$array of static method PHPUnit\\Framework\\Assert\:\:assertArrayNotHasKey\(\) expects array\\|ArrayAccess\<\(int\|string\), mixed\>, mixed given\.$#' identifier: argument.type diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 8bd4c043b0..e7c301345b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1925,6 +1925,12 @@ + + data->status['Aborted_clients']]]> + data->status['Aborted_connects']]]> + data->status['Connections']]]> + data->status['Max_used_connections']]]> + data->status['Aborted_clients']]]> data->status['Aborted_clients']]]> @@ -9256,6 +9262,10 @@ + + + + @@ -9275,9 +9285,6 @@ - - - @@ -9296,12 +9303,9 @@ - - - @@ -11924,7 +11928,6 @@ - diff --git a/src/Util.php b/src/Util.php index 4ed2ff3005..e577cc621a 100644 --- a/src/Util.php +++ b/src/Util.php @@ -47,6 +47,7 @@ use function is_object; use function is_scalar; use function is_string; use function log10; +use function max; use function mb_detect_encoding; use function mb_strlen; use function mb_strpos; @@ -365,11 +366,11 @@ class Util * echo formatNumber(0, 6); // 0 * * - * @param float|int|string $value the value to format - * @param int $digitsLeft number of digits left of the comma - * @param int $digitsRight number of digits right of the comma - * @param bool $onlyDown do not reformat numbers below 1 - * @param bool $noTrailingZero removes trailing zeros right of the comma (default: true) + * @param float|int|numeric-string $value the value to format + * @param int $digitsLeft number of digits left of the comma + * @param int $digitsRight number of digits right of the comma + * @param bool $onlyDown do not reformat numbers below 1 + * @param bool $noTrailingZero removes trailing zeros right of the comma (default: true) * * @return string the formatted value and its unit */ @@ -381,57 +382,26 @@ class Util bool $onlyDown = false, bool $noTrailingZero = true, ): string { - if ($value == 0) { + $value = (float) $value; + if ($value === 0.0) { return '0'; } - if (is_string($value)) { - $value = (float) $value; - } - - $originalValue = $value; - //number_format is not multibyte safe, str_replace is safe - if ($digitsLeft === 0) { - $value = number_format( - (float) $value, - $digitsRight, - /* l10n: Decimal separator */ - __('.'), - /* l10n: Thousands separator */ - __(','), - ); - if ($originalValue != 0 && (float) $value == 0) { - return ' <' . (1 / 10 ** $digitsRight); - } - - return $value; - } - - // this units needs no translation, ISO - $units = [ - -8 => 'y', - -7 => 'z', - -6 => 'a', - -5 => 'f', - -4 => 'p', - -3 => 'n', - -2 => 'µ', - -1 => 'm', - 0 => ' ', - 1 => 'k', - 2 => 'M', - 3 => 'G', - 4 => 'T', - 5 => 'P', - 6 => 'E', - 7 => 'Z', - 8 => 'Y', - ]; /* l10n: Decimal separator */ $decimalSep = __('.'); /* l10n: Thousands separator */ $thousandsSep = __(','); + //number_format is not multibyte safe, str_replace is safe + if ($digitsLeft === 0) { + $value = number_format($value, $digitsRight, $decimalSep, $thousandsSep); + if ((float) $value === 0.0) { + return '<' . number_format((1 / 10 ** $digitsRight), $digitsRight, $decimalSep, $thousandsSep); + } + + return $value; + } + // check for negative value to retain sign if ($value < 0) { $sign = '-'; @@ -443,9 +413,9 @@ class Util $dh = 10 ** $digitsRight; // This gives us the right SI prefix already, but $digits_left parameter not incorporated - $d = floor(log10((float) $value) / 3); + $d = floor(log10($value) / 3); // Lowering the SI prefix by 1 gives us an additional 3 zeros - // So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits) to use, then lower the SI prefix + // So if we have 3,6,9,12... free digits ($digits_left - $cur_digits) to use, then lower the SI prefix $curDigits = floor(log10($value / 1000 ** $d) + 1); if ($digitsLeft > $curDigits) { $d -= floor(($digitsLeft - $curDigits) / 3); @@ -455,6 +425,32 @@ class Util $d = 0; } + // SI unit prefixes need no translation + $units = [ + -10 => 'q', // quecto + -9 => 'r', // ronto + -8 => 'y', // yocto + -7 => 'z', // zepto + -6 => 'a', // atto + -5 => 'f', // femto + -4 => 'p', // pico + -3 => 'n', // nano + -2 => 'µ', // micro + -1 => 'm', // milli + 0 => '', + 1 => 'k', // kilo + 2 => 'M', // mega + 3 => 'G', // giga + 4 => 'T', // tera + 5 => 'P', // peta + 6 => 'E', // exa + 7 => 'Z', // zetta + 8 => 'Y', // yotta + 9 => 'R', // ronna + 10 => 'Q', // quetta + ]; + + $d = min(max((int) $d, -10), 10); $value = round($value / (1000 ** $d / $dh)) / $dh; $unit = $units[$d]; @@ -465,11 +461,13 @@ class Util $formattedValue = preg_replace('/' . preg_quote($decimalSep, '/') . '?0+$/', '', $formattedValue); } - if ($originalValue != 0 && $value == 0) { - return ' <' . number_format(1 / 10 ** $digitsRight, $digitsRight, $decimalSep, $thousandsSep) . ' ' . $unit; + if ($value === 0.0) { + return '<' + . number_format(1 / 10 ** $digitsRight, $digitsRight, $decimalSep, $thousandsSep) + . ($unit === '' ? '' : ' ' . $unit); } - return $sign . $formattedValue . ' ' . $unit; + return $sign . $formattedValue . ($unit === '' ? '' : ' ' . $unit); } /** diff --git a/tests/unit/Engines/InnodbTest.php b/tests/unit/Engines/InnodbTest.php index b1a66c4bdd..a7a6b927a9 100644 --- a/tests/unit/Engines/InnodbTest.php +++ b/tests/unit/Engines/InnodbTest.php @@ -194,7 +194,7 @@ class InnodbTest extends AbstractTestCase ' ' . "\n" . ' ' . "\n" . ' Read misses in %' . "\n" . - ' 50 %' . "\n" . + ' 50 %' . "\n" . '' . "\n" . ' ' . "\n" . ' ' . "\n" . diff --git a/tests/unit/UtilTest.php b/tests/unit/UtilTest.php index 91175f47b3..396f75e515 100644 --- a/tests/unit/UtilTest.php +++ b/tests/unit/UtilTest.php @@ -538,53 +538,25 @@ class UtilTest extends AbstractTestCase ]; } - /** - * Core test for formatNumber - * - * @param float|int|string $a Value to format - * @param int $b Sensitiveness - * @param int $c Number of decimals to retain - * @param string $d Expected value - */ - private function assertFormatNumber(float|int|string $a, int $b, int $c, string $d): void - { - self::assertSame( - $d, - Util::formatNumber( - $a, - $b, - $c, - false, - ), - ); - } - - /** - * format number test, globals are defined - * - * @param float|int|string $a Value to format - * @param int $b Sensitiveness - * @param int $c Number of decimals to retain - * @param string $d Expected value - */ + /** @psalm-param list{0: float|int|numeric-string, 1?: int, 2?: int, 3?: bool, 4?: bool} $arguments */ #[DataProvider('providerFormatNumber')] - public function testFormatNumber(float|int|string $a, int $b, int $c, string $d): void + public function testFormatNumber(string $expected, array $arguments): void { - $this->assertFormatNumber($a, $b, $c, $d); + self::assertSame($expected, Util::formatNumber(...$arguments)); // Test with various precisions - $oldPrecision = (string) ini_get('precision'); + $oldPrecision = ini_get('precision'); try { ini_set('precision', '20'); - $this->assertFormatNumber($a, $b, $c, $d); + self::assertSame($expected, Util::formatNumber(...$arguments)); ini_set('precision', '14'); - $this->assertFormatNumber($a, $b, $c, $d); + self::assertSame($expected, Util::formatNumber(...$arguments)); ini_set('precision', '10'); - $this->assertFormatNumber($a, $b, $c, $d); + self::assertSame($expected, Util::formatNumber(...$arguments)); ini_set('precision', '5'); - $this->assertFormatNumber($a, $b, $c, $d); + self::assertSame($expected, Util::formatNumber(...$arguments)); ini_set('precision', '-1'); - $this->assertFormatNumber($a, $b, $c, $d); + self::assertSame($expected, Util::formatNumber(...$arguments)); } finally { ini_set('precision', $oldPrecision); } @@ -593,17 +565,28 @@ class UtilTest extends AbstractTestCase $translator = Loader::getInstance()->getTranslator(); try { + $translator->setTranslation('.', '/'); + self::assertSame( + str_replace('.', '/', $expected), + Util::formatNumber(...$arguments), + 'Decimal separator should be escaped for regex.', + ); + // German $translator->setTranslation(',', '.'); $translator->setTranslation('.', ','); - $expected = str_replace([',', 'X'], ['.', ','], str_replace('.', 'X', $d)); - $this->assertFormatNumber($a, $b, $c, $expected); + self::assertSame( + str_replace([',', 'X'], ['.', ','], str_replace('.', 'X', $expected)), + Util::formatNumber(...$arguments), + ); // Czech $translator->setTranslation(',', ' '); $translator->setTranslation('.', ','); - $expected = str_replace([',', 'X'], [' ', ','], str_replace('.', 'X', $d)); - $this->assertFormatNumber($a, $b, $c, $expected); + self::assertSame( + str_replace([',', 'X'], [' ', ','], str_replace('.', 'X', $expected)), + Util::formatNumber(...$arguments), + ); } finally { // Restore $translator->setTranslation(',', ','); @@ -611,32 +594,57 @@ class UtilTest extends AbstractTestCase } } - /** - * format number data provider - * - * @return mixed[] - */ + /** @psalm-return array */ public static function providerFormatNumber(): array { return [ - [10, 2, 2, '10 '], - [100, 2, 0, '100 '], - [100, 2, 2, '100 '], - ['100', 2, 2, '100 '], - [-1000.454, 4, 2, '-1,000.45 '], - ['-1000.454', 4, 2, '-1,000.45 '], - [0.00003, 3, 2, '30 µ'], - [0.003, 3, 3, '3 m'], - [-0.003, 6, 0, '-3,000 µ'], - [100.98, 0, 2, '100.98'], - [21010101, 0, 2, '21,010,101.00'], - [1100000000, 5, 0, '1,100 M'], - ['1100000000', 5, 0, '1,100 M'], - [20000, 2, 2, '20 k'], - [20011, 2, 2, '20.01 k'], - [123456789, 6, 0, '123,457 k'], - [-123456789, 4, 2, '-123.46 M'], - [0, 6, 0, '0'], + ['10', [10, 2, 2]], + ['100', [100, 2, 0]], + ['100', [100, 2, 2]], + ['100', ['100', 2, 2]], + ['-1,000.45', [-1000.454, 4, 2]], + ['-1,000.45', ['-1000.454', 4, 2]], + ['30 µ', [0.00003, 3, 2]], + ['3 m', [0.003, 3, 3]], + ['-3,000 µ', [-0.003, 6, 0]], + ['100.98', [100.98, 0, 2]], + ['21,010,101.00', [21010101, 0, 2]], + ['1,100 M', [1100000000, 5, 0]], + ['1,100 M', ['1100000000', 5, 0]], + ['20 k', [20000, 2, 2]], + ['20.01 k', [20011, 2, 2]], + ['123,457 k', [123456789, 6, 0]], + ['-123.46 M', [-123456789, 4, 2]], + ['0', [0]], + ['0', [0.0]], + ['0', ['0']], + ['0', ['0.0']], + ['<0.001', [0.000001, 0, 3]], + ['5 m', [0.005]], + ['5 µ', [0.000005]], + ['5 n', [0.000000005]], + ['5 p', [0.000000000005]], + ['5 f', [0.000000000000005]], + ['5 a', [0.000000000000000005]], + ['5 z', [0.000000000000000000005]], + ['5 y', [0.000000000000000000000005]], + ['5 r', [0.000000000000000000000000005]], + ['5 q', [0.000000000000000000000000000005]], + ['<1 q', [0.000000000000000000000000000000005]], + ['5 k', [5000]], + ['5 M', [5000000]], + ['5 G', [5000000000]], + ['5 T', [5000000000000]], + ['5 P', [5000000000000000]], + ['5 E', [5000000000000000000]], + ['5 Z', [5000000000000000000000]], + ['5 Y', [5000000000000000000000000]], + ['5 R', [5000000000000000000000000000]], + ['5 Q', [5000000000000000000000000000000]], + ['5,000 Q', [5000000000000000000000000000000000]], + ['100 m', [0.1, 3, 0]], + ['<1', [0.1, 3, 0, true]], + ['1.000 k', [1000, 3, 3, false, false]], ]; }