利用区域设置独立性

在 PHP 8 之前的 PHP 版本中,有几个字符串函数和操作与本地语言有关。这样做的结果是,数字的内部存储方式因地域而异。这种做法带来了难以察觉的微妙的不一致性。在学习了本章介绍的内容后,您将能更好地检测 PHP 8 升级后潜在的应用程序代码更改,从而避免应用程序失败。

了解与区域设置相关的问题

在早期的 PHP 版本中,本地化依赖性会产生一个令人遗憾的副作用,那就是当类型转换从浮点数到字符串再返回浮点数时,会出现不一致的结果。将浮点数值连接到字符串时也会出现不一致的结果。OpCache 执行的某些优化操作会导致连接操作在本地设置之前发生,这也是可能产生不一致结果的另一种方式。

在 PHP 8 中,易受影响的操作和函数现在与本地无关了。这意味着所有浮点数值现在都使用句点作为小数分隔符。默认 locale 不再从环境中继承。如果需要设置默认 locale,现在必须明确调用 setlocale()

审查受区域设置独立性影响的函数和操作

大多数 PHP 函数都不会受到本地化无关性转换的影响,原因很简单,本地化与函数或扩展无关。此外,大多数 PHP 函数和扩展已经是本地独立的了。例如 PDO 扩展、var_export()json_encode() 等函数以及 printf() 系列。

受本地独立影响的函数和操作包括以下:

  • (string) $float

  • strval($float)

  • print_r($float)

  • var_dump($float)

  • debug_zval_dump($float)

  • settype($float, "string")

  • implode([$float])

  • xmlrpc_encode($float)

下面是一个代码示例,说明如何处理因地域独立性而产生的差异:

  1. 首先,我们定义一个要测试的本地数组。所选的本地语言使用不同的方式表示数字的小数部分:

    // /repo/ch06/php8_locale_independent.php
    $list = ['en_GB', 'fr_FR', 'de_DE'];
    $patt = "%15s | %15s \n";
  2. 然后,我们循环遍历本地化,设置本地化,执行浮点数到字符串的转换,然后是字符串到浮点数的转换,每一步都对结果进行回声处理:

    foreach ($list as $locale) {
        setlocale(LC_ALL, $locale);
        echo "Locale : $locale\n";
        $f = 123456.789;
        echo "Original : $f\n";
        $s = (string) $f;
        echo "Float to String : $s\n";
        $r = (float) $s;
        echo "String to Float : $r\n";
    }

如果我们在 PHP 7 中运行此示例,请注意结果:

root@php8_tips_php7 [ /repo/ch06 ]#
php php8_locale_independent.php
Locale : en_GB
Original : 123456.789
Float to String : 123456.789
String to Float : 123456.789
Locale : fr_FR
Original : 123456,789
Float to String : 123456,789
String to Float : 123456
Locale : de_DE
Original : 123456,789
Float to String : 123456,789
String to Float : 123456

从输出结果可以看出,在内部存储数字时,en_GB 使用句点作为小数分隔符,而 fr_FR 和 de_DE 则使用逗号。但是,当字符串转换回数字时,如果小数分隔符不是句号,字符串就会被当作前导数字字符串处理。在其中两种语言中,逗号的存在会停止转换过程。最终结果是,小数部分被放弃,精度丢失。

在 PHP 8 中运行相同代码示例的结果如下所示:

root@php8_tips_php8 [ /repo/ch06 ]#
php php8_locale_independent.php
Locale : en_GB
Original : 123456.789
Float to String : 123456.789
String to Float : 123456.789
Locale : fr_FR
Original : 123456.789
Float to String : 123456.789
String to Float : 123456.789
Locale : de_DE
Original : 123456.789
Float to String : 123456.789
String to Float : 123456.789

在 PHP 8 中,数字的精确度不会降低,而且无论在哪个地区,数字都会一致地使用句点作为小数分隔符。

请注意,您仍然可以通过使用 number_format() 函数或使用 NumberFormatter 类(来自 Intl 扩展)来根据本地语言表示数字。值得注意的是,NumberFormatter 类内部存储数字的方式与本地无关!

更多信息,请参阅本文: https://wiki.php.net/rfc/locale_independent_float_to_string

有关国际号码格式化的更多信息,请参阅以下链接: https://www.php.net/manual/en/class.numberformatter.php

现在您已经了解了 PHP 8 中与本地无关的方面,我们需要看看数组处理方面的变化。