Refactor Util::formatNumber()

Trim whitespace and add more unit prefixes.

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2025-07-24 10:41:36 -03:00
parent ba1743bdc0
commit 09e8144e97
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
5 changed files with 81 additions and 73 deletions

View File

@ -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

View File

@ -1925,6 +1925,12 @@
</PossiblyUnusedReturnValue>
</file>
<file src="src/Controllers/Server/Status/StatusController.php">
<ArgumentTypeCoercion>
<code><![CDATA[$this->data->status['Aborted_clients']]]></code>
<code><![CDATA[$this->data->status['Aborted_connects']]]></code>
<code><![CDATA[$this->data->status['Connections']]]></code>
<code><![CDATA[$this->data->status['Max_used_connections']]]></code>
</ArgumentTypeCoercion>
<PossiblyInvalidOperand>
<code><![CDATA[$this->data->status['Aborted_clients']]]></code>
<code><![CDATA[$this->data->status['Aborted_clients']]]></code>
@ -9256,6 +9262,10 @@
</MixedArgument>
</file>
<file src="src/Util.php">
<ArgumentTypeCoercion>
<code><![CDATA[$value]]></code>
<code><![CDATA[$value]]></code>
</ArgumentTypeCoercion>
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
@ -9275,9 +9285,6 @@
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
</DeprecatedMethod>
<InvalidArrayOffset>
<code><![CDATA[$units[$d]]]></code>
</InvalidArrayOffset>
<MixedArgument>
<code><![CDATA[$array]]></code>
</MixedArgument>
@ -9296,12 +9303,9 @@
<MixedAssignment>
<code><![CDATA[$array]]></code>
<code><![CDATA[$columnNames[]]]></code>
<code><![CDATA[$unit]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
<MixedOperand>
<code><![CDATA[$unit]]></code>
<code><![CDATA[$unit]]></code>
<code><![CDATA[$value]]></code>
</MixedOperand>
<MixedReturnStatement>

View File

@ -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
* </code>
*
* @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,51 +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;
}
/* l10n: Decimal separator */
$decimalSep = __('.');
/* l10n: Thousands separator */
$thousandsSep = __(',');
$originalValue = $value;
//number_format is not multibyte safe, str_replace is safe
if ($digitsLeft === 0) {
$value = number_format((float) $value, $digitsRight, $decimalSep, $thousandsSep);
if ($originalValue != 0 && (float) $value == 0) {
return ' <' . number_format((1 / 10 ** $digitsRight), $digitsRight, $decimalSep, $thousandsSep);
$value = number_format($value, $digitsRight, $decimalSep, $thousandsSep);
if ((float) $value === 0.0) {
return '<' . number_format((1 / 10 ** $digitsRight), $digitsRight, $decimalSep, $thousandsSep);
}
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',
];
// check for negative value to retain sign
if ($value < 0) {
$sign = '-';
@ -437,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);
@ -449,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];
@ -459,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);
}
/**

View File

@ -194,7 +194,7 @@ class InnodbTest extends AbstractTestCase
' </tr>' . "\n" .
' <tr>' . "\n" .
' <th scope="row">Read misses in %</th>' . "\n" .
' <td class="font-monospace text-end">50 %' . "\n" .
' <td class="font-monospace text-end">50 %' . "\n" .
'</td>' . "\n" .
' </tr>' . "\n" .
' <tr>' . "\n" .

View File

@ -538,7 +538,7 @@ class UtilTest extends AbstractTestCase
];
}
/** @psalm-param list{0: float|int|string, 1?: int, 2?: int, 3?: bool, 4?: bool} $arguments */
/** @psalm-param list{0: float|int|numeric-string, 1?: int, 2?: int, 3?: bool, 4?: bool} $arguments */
#[DataProvider('providerFormatNumber')]
public function testFormatNumber(string $expected, array $arguments): void
{
@ -594,16 +594,16 @@ class UtilTest extends AbstractTestCase
}
}
/** @psalm-return array<array{string, list{0: float|int|string, 1?: int, 2?: int, 3?: bool, 4?: bool}}> */
/** @psalm-return array<array{string, list{0: float|int|numeric-string, 1?: int, 2?: int, 3?: bool, 4?: bool}}> */
public static function providerFormatNumber(): array
{
return [
['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]],
['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]],
@ -619,7 +619,7 @@ class UtilTest extends AbstractTestCase
['0', [0.0]],
['0', ['0']],
['0', ['0.0']],
[' <0.001', [0.000001, 0, 3]],
['<0.001', [0.000001, 0, 3]],
['5 m', [0.005]],
['5 µ', [0.000005]],
['5 n', [0.000000005]],
@ -628,6 +628,9 @@ class UtilTest extends AbstractTestCase
['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]],
@ -636,8 +639,11 @@ class UtilTest extends AbstractTestCase
['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', [0.1, 3, 0, true]],
['1.000 k', [1000, 3, 3, false, false]],
];
}