编程

PHP 8.2:弃用特性 — utf8_encode 及 utf8_decode 函数弃用

2303 2022-11-21 05:16:51

utf8_encodeutf8_decode 函数,用于将字符串在 ISO-8859-1 (也叫“Latin 1”) 和 UTF-8 之间互相转换。这些函数不会尝试检测给定的文本的实际符号,且总是在 ISO-8859-1 和 UTF-8 中相互转换,即使源文本并非使用 ISO-8859-1 进行编码。

虽然 PHP 在其标准库中包含了 utf8_encodeutf8_decode 函数,这些函数不能用于检测和转换其他编码方式的文本,比如将 Windows-1252、UTF-16 和 UTF-32 转码为 UTF-8。传入任意文本到 utf8_encode 函数,容易产生 bug,并且不会产生任何警告及错误,不过却带来了不需要的结果。

比较频繁的一些 Bug 的例子包括:

  • 欧元标志 (€,字符序列 \xE2\x82\xAC),当其传入到 utf8_encode 函数,会导致输入杂乱文本(也叫乱码):â¬。
  • 德文字符 (ß, 字符序列 \xDF),当使用 utf8_encode("ß") 进行编码时会输出 Ã。

上面的两个例子都不会出现任何警告或者错误,尽管它们输出的文本是错误的。

因为函数名误导、缺乏错误信息及警告、缺乏对 ISO-8859-1 之外的字符串的支持,utf8_encodeutf8_decode 函数在 PHP 8.2 中被弃用了

在 PHP 8.2 中使用 utf8_encodeutf8_decode 函数会出现废弃通知,这些函数在 PHP 9.0 中会被移除。

utf8_encode('foo');
uft8_decode('foo');
Function utf8_encode() is deprecated in ... on line ...
Function uft8_decode() is deprecated in ... on line ...

替换弃用函数

utf8_encode 函数将 ISO-8859-1 编码的字符串转换成 UTF-8。在旧版 PHP 应用中大部分 utf8_encode 函数的调用将其作为传统的安全保障,以避免出现任何潜在畸形文本,不过如上述的例子,使用这一函数经常会带来不需要的输出。

相似地,调用 utf8_decode 函数用来解码字符串转码到 ISO-8859-1 字符编码。大部分的网站应用网站、文本格式实际上使用 UTF-8 编码而不是 ISO-8859-1。

在替代前优先重新评估是否需要调用 utf8_encodeutf8_decode 函数,因为更经常的情况是,这些函数的调用并不需要,而且只会带来不需要的输出。

PHP 在它的内核中没有捆绑多字节字符编码函数,不过 PHP 核心的 mbstringintliconv 扩展提供了健壮且准确的功能,用来检测和转换字符编码。mbstringiconv 都是核心扩展,mbstring 广泛用于现代 PHP 应用,可以 polyfill 补丁更新。

替换 utf8_encode

如果实际的应用中存在将已知的 ISO-8859-1 编码字符转成 UTF-8,可以使用 iconvintl 或者 mbstring 扩展对其转码。另外,尽管有些性能损耗,也可以使用用户空间 PHP 直接将代码点转换成 UTF-8 字符。

当使用的 utf8_encode 自动检测字符编码并转成 UTF-8 的用例时,尽管该函数一开始并没有检测字符编码,替代的函数还是需要先检测编码再转换成 UTF-8。

 ISO-8859-1 to UTF-8其他编码转换成 UTF-8
PHP 标准函数使用 标准 PHP 函数将 ISO-8859-1 转成 UTF-8 N/A
使用 mbstring使用 mbstring 将 ISO-8859-1 转成 UTF-8 使用 mbstring 将其他编码转换成 UTF-8 
使用 intl使用 intl 将 ISO-8859-1 转成 UTF-8 N/A
使用 iconv使用 iconv 将 ISO-8859-1 转成 UTF-8 N/A

使用标准函数将 ISO-8859-1 转成 UTF-8

symfony/polyfill-php72 库提供了使用 PHP 标准函数模仿 utf8_encode 功能。为了让他更具可读性同时传达该函数的含义,将其重命名为 iso8859_1_to_utf8

function iso8859_1_to_utf8(string $string): string {
    $s .= $string;
    $len = \strlen($s);

    for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
        switch (true) {
            case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
            case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
            default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
        }
    }

    return substr($s, 0, $j);
}

有了上述的函数,就可以将所有 utf8_encode 调用替换成新增的 iso8859_1_to_utf8 函数,以避免弃用通知:

- utf8_encode($string);
+ iso8859_1_to_utf8($string);

使用 mbstring 将 ISO-8859-1 转换成 UTF-8 

mbstring 扩展,最广泛使用的 PHP 可选扩展,提供了更简洁和直接的方法将 ISO-8859-1 编码的字符串转成 UTF-8。这也能用来替换 PHP 8.2 中弃用的 utf8_encode 函数。

- utf8_encode($string);
+ mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1');

使用 mbstring 将其他编码转换成 UTF-8 

未实际了解输入文本的实际编码方式,可能再 PHP 强制检测字符编码时导致错误的结果。不过,可以合理猜测源文本编码并使用 mbstring 将其转换成 UTF-8。

- utf8_encode($string);
+ mb_convert_encoding($string, 'UTF-8', mb_list_encodings());

使用 intl 将 ISO-8859-1 转成 UTF-8

intl 扩展的 UConverter 类也提供了对字符编码进行转换的方法。它提供了与 mbstring 相似的函数签名。使用 UConverter::transcode,可以替代 utf8_encode 功能:

- utf8_encode($string);
+ UConverter::transcode($latin1, 'UTF8', 'ISO-8859-1');

使用 iconv 将 ISO-8859-1 转成 UTF-8

使用 iconv 扩展的应用可以用 iconv 函数替换 utf8_encode 函数:

- utf8_encode($string);
+ iconv('ISO-8859-1', 'UTF-8', $string);

替换 utf8_decode

utf8_decode 函数将 UTF-8 编码的字符串解码转成 ISO-8859-1。随着 utf8_decode 函数弃用,可以使用 PHP 标准函数、mbstring 扩展、intl 扩展或 iconv 扩展替代该函数。

 UTF-8 转成 ISO-8859-1
PHP 标准函数使用标准PHP函数将UTF-8 转成 ISO-8859-1
使用 mbstring使用 mbsting 将UTF-8 转成 ISO-8859-1 
使用 intl使用 intl 将UTF-8 转成 ISO-8859-1 
使用 iconv使用 iconv 将UTF-8 转成 ISO-8859-1 

使用标准 PHP 函数将 UTF-8 转成 ISO-8859-1

类似于 utf8_encode polyfillsymfony/polyfill-php72 库也提供了一个模仿 utf8_decode 函数的 PHP 函数:

function utf8_to_iso8859_1(string $string): string {
    $s = (string) $string;
    $len = \strlen($s);

    for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
        switch ($s[$i] & "\xF0") {
            case "\xC0":
            case "\xD0":
                $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F");
                $s[$j] = $c < 256 ? \chr($c) : '?';
                break;

            case "\xF0":
                ++$i;
                // no break

            case "\xE0":
                $s[$j] = '?';
                $i += 2;
                break;

            default:
                $s[$j] = $s[$i];
        }
    }

    return substr($s, 0, $j);
}

引入上述函数,可以用新增的 utf8_to_iso8859_1 替换掉 tf8_decode 的调用:

- utf8_decode($string);
+ utf8_to_iso8859_1($string);

使用 mbsting 将 UTF-8 转成 ISO-8859-1 

下面是使用 mb_convert_encoding 函数代替 utf8_decode 的例子:

- utf8_decode($string);
+ mb_convert_encoding($string, 'ISO-8859-1', 'UTF-8');

使用 intl 将 UTF-8 转成 ISO-8859-1 

使用 intl 扩展的 UConverter::transcode,可以替换 utf8_decode 调用:

- utf8_encode($string);
+ UConverter::transcode($string, 'ISO-8859-1', 'UTF8', ['to_subst' => '?']);

使用 iconv 将 UTF-8 转成 ISO-8859-1 

iconv 函数也能用于模仿和替换 utf8_decode 函数,以避免 PHP 8.2 中的弃用通知:

- utf8_encode($string);
+ iconv('UTF-8', 'ISO-8859-1', $string);

向后兼容性影响

utf8_encodeutf8_decode 函数在旧版 PHP 应用中有时候会用到,用来处理传入的使用各种编码的数据和文件。因为误导性的名字以及会带来预期之外的结果而不显式警告或报错,这些函数在 PHP 8.2 中会被弃用,在 PHP 9.0 中会被移除。 

PHP 8.2 中,使用这两个函数每次调用都会导致弃用通知。

utf8_encodeutf8_decoden 函数在 PHP 9.0 中会被移除。

很多使用这两个函数的应用,并不清楚它们只能转换 ISO-8859-1 编码。因此先了解为什么使用这两个函数,再确定是否真的需要它们,可能是更好的修复方式。

可以根据应用所依赖的 PHP 扩展,决定如何替换 utf8_encodeutf8_decode 函数的调用。