编程

PHP 8.2 特性 - 新增 ini_parse_quantity 函数

745 2022-12-05 12:35:27

PHP INI 指令接受可包含后缀的数据大小,用以指定单位乘数,比如 5M或者 1G。这些后缀在 PHP INI 文件中广泛使用,不过并不是国际标准后缀。 

ini_parse_quantity 是在 PHP 8.2 中新加入 PHP 的函数。该函数解析由 PHP INI 值可以识别的任何数据大小(比如 56K,256M,1G) 并以 byte 返回数据大小(size)。该函数在解析现有的或提供的 INI 值时会非常有用。

ini_parse_quantity 函数不能识别 IEC 后缀,比如 MB、MiB 或 GB,并且不适于将标准数据大小值转成 byte。

比如,PHP INI  和 ini_parse_quantity 函数中的 1K 被当成是 1KB,也就是 1024 byte。ini_parse_quantity 返回解析后的 byte 值。

ini_parse_quantity 函数概要

function ini_parse_quantity(string $shorthand): int {
}

ini_parse_quantity 函数通过检测字符后缀尝试解析给定的 $shorthand 值,返回值会乘以指定单位对应的数值。

下表是后缀对应的名称和乘数值:

SuffixNameMultiplier
k / KKibibytes1024
m / MMegabytes1048576
g / GGigabytes1073741824

请注意该函数在碰到不能识别的后缀时(e.g 53Q),将会发送警告;在碰到解析的 byte 值溢出(9999999999G)或如果解析器忽视未识别的字符时(12RG),没有前导数字(R2D2)。并且不会抛出任何异常,调用者可能需要去检查警告信息,确保值可以正确解析。

另外,该函数不能识别小数值(1.5G)。

ini_parse_quantity 函数 (及 PHP 内部解析的 INI value ) 用于尝试绕过错误格式的字符串,使之不会抛出异常。如果该函数最终不能解析某个值,则会返回 0。

函数警告信息

ini_parse_quantity 函数如果碰到不符合格式的值时,会发送多种不同类型的警告。

  • 不带前导数字的值会返回 0
    不带前导数字的值,比如 ABC、T5、S05e06,将会无效。会产生警告并返回 0 : 

后缀不可识别的值 返回最先发现的整数值
函数不饿能识别的单位会被忽略,并且返回传入值中包含的绝对整型值。

  • 123R2D2 解析为 "123"
  • 1234T 解析为 "123"
  • -1234T 解析为 "-123"

值的中间包含不可识别的字符时 返回数量值并发出警告
如果解析的值包含移除的字符,会发出警告,不过函数还是返回解析后的量化值。

  • 123FG 被解析成 "123G" 对应 132070244352
  • 123$M 被解析成 "123M" 对应 44040192
  • -123MM 被解析成 "-123M" 对应 -128974848

溢出的值 返回溢出后的值
如果计算的值超出 CPU 所能处理的最大、最小整型值,会发出警报,并且函数会返回溢出值。

  • 9999999999G 被计算为 -7709325834783293440
  • -9999999999G 被计算为 7709325834783293440

用户空间的 PHP 实现

下例是 PHP 8.2 的 ini_parse_quantity 函数在 PHP 用户空间中的实现,可以在 PHP 7.0 及以上版本中运行。与 PHP 8.2 的 ini_parse_quantity 发出同样的警告。

function ini_parse_quantity(string $shorthand): int {
    $original_shorthand = $shorthand;
    $multiplier = 1;
    $sign = '';
    $return_value = 0;

    $shorthand = trim($shorthand);

    // Return 0 for empty strings.
    if ($shorthand === '') {
        return 0;
    }

    // Accept + and - as the sign.
    if ($shorthand[0] === '-' || $shorthand[0] === '+') {
        if ($shorthand[0] === '-') {
            $multiplier = -1;
            $sign = '-';
        }
        $shorthand = substr($shorthand, 1);
    }

    // If there is no suffix, return the integer value with the sign.
    if (preg_match('/^\d+$/', $shorthand, $matches)) {
        return $multiplier * $matches[0];
    }

    // Return 0 with a warning if there are no leading digits
    if (preg_match('/^\d/', $shorthand) === 0) {
        trigger_error(sprintf('Invalid quantity "%s": no valid leading digits, interpreting as "0" for backwards compatibility', $original_shorthand), E_USER_WARNING);
        return $return_value;
    }

    // Removing whitespace characters.
    $shorthand = preg_replace('/\s/', '', $shorthand);

    $suffix = strtoupper(substr($shorthand, -1));
    switch ($suffix) {
        case 'K':
            $multiplier *= 1024;
            break;
        case 'M':
            $multiplier *= 1024 * 1024;
            break;
        case 'G':
            $multiplier *= 1024 * 1024 * 1024;
            break;
        default:
            preg_match('/\d+/', $shorthand, $matches);
            trigger_error(sprintf('Invalid quantity "%s": unknown multiplier "%s", interpreting as "%d" for backwards compatibility', $original_shorthand, $suffix, $sign . $matches[0]), E_USER_WARNING        );
            return $matches[0] * $multiplier;
    }

    $stripped_shorthand = preg_replace('/^(\d+)(\D.*)([kKmMgG])$/', '$1$3', $shorthand, -1, $count);
    if ($count > 0) {
        trigger_error(sprintf('Invalid quantity "%s", interpreting as "%s" for backwards compatibility', $original_shorthand, $sign . $stripped_shorthand), E_USER_WARNING);
    }

    preg_match('/\d+/', $shorthand, $matches);

    $multiplied = $matches[0] * $multiplier;
    if (is_float($multiplied)) {
        trigger_error(sprintf('Invalid quantity "%s": value is out of range, using overflow result for backwards compatibility', $original_shorthand), E_USER_WARNING);
    }

    return (int) ($matches[0] * $multiplier);
}

向下兼容性影响

ini_parse_quantity 是 PHP 8.2 中新增的函数。该函数与映射到 PHP 引擎的一些内部代码一起添加,会导致解析 INI 文件时发出警告。

不过,如果应用或者库想使其在旧版本中使用,该函数可以在用户空间代码中实现。

Invalid quantity "S06e08": no valid leading digits, interpreting as "0" for backwards compatibility