尝试在PHP 7.2开发版中引入的mb_chr,mb_ord,mb_scrub


概述

mb_chrmb_ordmb_scrub已在PHP 7.2的开发版本(截至2016年9月的主分支)中引入。我(masakielastic)提出了这些功能的实现,然后由yohgaki验证并介绍了它们。

PHP源代码NEWS文件显示:

    • 已实现的请求#66024(mb_chr()和mb_ord())(Masakielastic,Yasuo)
    • 已实现的请求#65081(mb_scrub())(Masakielastic,Yasuo)

使用polyfill-mbstring

如果您没有安装

mbstring或要在PHP 7.2或更早版本中采用mb_chrmb_ordmb_scrub,则可以选择安装Symfony的polyfill-mbstring组件。

通过Composer

1
composer require symfony/polyfill-mbstring

但是,并非本文??中介绍的所有功能都已被复制。

生成开发版本

创建仅包含最新历史记录的浅表克隆。

1
2
3
4
5
git clone --depth=1 [email protected]:php/php-src.git
cd php-src
./buildconf
./configure --enable-mbstring
make

确保

mbstring模块已加载。

1
sapi/cli/php -m | grep mbstring

启用国际模块

如果要使用PHP 7.0中引入的IntlChar::chrIntlChar::ord进行比较,请添加构建选项以启用intl模块。

1
./configure --enable-mbstring --enable-intl

取消整理和更新存储库

执行

git fetchgit pull时,指定--unshallow选项以取消浅色。除非清除浅点,否则不会是merge

1
git pull --unshallow

检查功能是否可用

运行以下命令以查看是否看到小写的" a"。

1
sapi/cli/php -r 'echo mb_chr(0x61), PHP_EOL;'

现在,让我们准备一个脚本文件并执行它。

1
sapi/cli/php test.php

test.php

1
2
3
4
var_dump(
    'a' === mb_chr(0x61),
    0x61 === mb_ord('a')
);

PHP 5.6和更高版本的php.ini指令

有关规范更改的详细信息,请参阅yohgaki开发的RFC。

PHP 5.6和更高版本的mbstring.internal_encodingmbstring.http_inputmbstring.http_output已弃用。如果您未设置这些指令的值,将使用default_charset的值。 default_charset的默认值为UTF-8

1
2
3
var_dump(
    'UTF-8' === ini_get('default_charset')
);

从PHP 5.6开始,default_charset用作htmlspecialchars字符编码的默认值。在PHP 5.4和5.5中,htmlspecialchars的字符编码的默认值为UTF-8

同时添加了internal_encodinginput_encodingoutput_encoding指令。如果这些默认值为空,将使用default_charset的值。

根据

RFC,internal_encoding用作htmlentitiesmb_strlenmb_regex之类的功能的默认值,但根据2016年9月的调查,使用ini_set。更改指令的值未反映在mb_internal_encoding的返回值中。

1
2
3
4
5
ini_set('internal_encoding', 'Shift_JIS');

var_dump(
    'UTF-8' === mb_internal_encoding()
);

功能规格

mb_chr

1
string mb_chr(int $codepoint[, string $encoding = mb_internal_encoding()])

mb_chr是将代码点转换为字符的功能。功能参数为$codepoint$encoding$codepoint是代码点。代码点是代表一个字符的整数值。代码点值因字符编码而异,例如UTF-8或Shift_JIS。 $encoding可以省略。

函数的返回值是与代码点相对应的字符。

让我们先尝试UTF-8。

1
2
3
var_dump(
    "あ" === mb_chr(0x3042)
);

如果希望在未安装日语字体或表情符号的终端上打开脚本代码,则可以使用PHP 7.0中引入的Unicode转义序列。

1
2
3
var_dump(
    "\u{3042}" === mb_chr(0x3042)
);

Shift_JIS的情况下,组成字符的字节字符串按原样以十六进制表示。

1
2
3
4
5
6
7
$char = mb_convert_encoding('あ', 'Shift_JIS', 'UTF-8');

var_dump(
   $char === mb_chr(0x82a0, 'Shift_JIS'),
   "82a0" === bin2hex(mb_chr(0x82a0, 'Shift_JIS')),
   "\x82\xa0" === mb_chr(0x82a0, 'Shift_JIS')
);

传统编码的最大字节数为4个字节。如果有更多字节的情况,请告诉我。尝试使用汉字编码GB18030的表情符号。我没有安装中文字体的设备,所以我没有看到它的外观。

1
2
3
4
5
6
7
// U+1F418: Elephant
$char = mb_convert_encoding("\u{1F418}", 'GB18030', 'UTF-8');

var_dump(
   $char === mb_chr(0x9439cb38, 'GB18030'),
   "\x94\x39\xCB\x38" === mb_chr(0x9439cb38, 'GB18030')
);

现在让我们尝试UTF-8无效代码点。返回值是与mbstring.substitute_character的值相对应的字符。默认值为U 003F。

1
2
3
4
var_dump(
    '?' === mb_chr(-1),
    "\u{003f}" === mb_chr(-1)
);

UTF-8将U FFFD定义为替代字符。

1
2
3
4
5
mb_substitute_character(0xfffd);

var_dump(
    "\u{fffd}" === mb_chr(-1)
);

如果指定了特定于通信或转换目的的字符编码,例如

ISO-2022-JPBASE64,将发出警告,返回值为false

1
var_dump(false === mb_chr(0x61, 'ISO-2022-JP'));

mb_ord

1
int mb_ord(string $char[, string $encoding = mb_internal_encoding()])

mb_ord是将字符转换为代码点的功能。 mb_ord的参数是$char$encoding$char是字符,$encoding可以省略。返回值是与$char对应的代码点。

让我们先尝试UTF-8。

1
2
3
4
var_dump(
    0x3042 === mb_ord('あ'),
    0x3042 === mb_ord("\u3042")
);

如果指定了无效的字节字符串,则将返回mbstring.substitute_character的值。

1
2
3
var_dump(
    0x3f === mb_ord("\x80")
);

尝试使用其他字符U FFFD。

1
2
3
4
5
mb_substitute_character(0xfffd);

var_dump(
    0xfffd === mb_ord("\x80")
);

mb_scrub

1
string mb_scrub(string $str[, string $encoding = mb_internal_encoding()])

mb_scrub是使用替代字符替换字符串中包含的无效字节字符串的函数。磨砂膏的含义是"去除污垢和污渍,用磨砂膏洗涤"。它是从Ruby 2.1.0中引入的String#scrub方法的名称采用的。

mb_scrub的参数是$str$encoding$encoding可以省略。返回值是参数字符串中包含的无效字节字符串,并用替换字符替换。 mbstring.substitute_character的值用作替代字符。

1
2
3
var_dump(
    "あ?" === mb_scrub("あ\x80")
);

尝试另一个替代字符。

1
2
3
4
5
mb_substitute_character(0xfffd);

var_dump(
    "あ\u{fffd}" === mb_scrub("あ\x80")
);

您也可以使用mb_convert_encoding代替

mb_scrub来替换无效的字节字符串。

1
2
3
4
var_dump(
    "あ?" === mb_scrub("あ\x80"),
    "あ?" === mb_convert_encoding("あ\x80", mb_internal_encoding())
);

通过组合

标准功能htmlspecialcharshtmlspecialchars_decode,可以完成相同的过程。

1
2
3
var_dump(
    "あ&\u{fffd}" === htmlspecialchars_decode(htmlspecialchars("あ&\x80", ENT_SUBSTITUTE))
);

如果已安装

intl模块,则可以使用UConverter。此类是在PHP 5.5时代引入的。

1
2
3
4
var_dump(
    "あ\u{fffd}" === UConverter::transcode("あ\x80", 'UTF-8', 'UTF-8'),
    "あ\u{fffd}" === (new UConverter('UTF-8', 'UTF-8'))->convert("あ\x80")
);

开发者说明

UTF-8基础知识

UTF-8有效代码点的范围是U 0000到U D7FF和U E 000到U 10FFFF。从U D800到U DFFF的范围称为代理字符。由于UTF-16只能使用不超过U FFFF的代码点,因此我们使用代理对(两个代理字符)来表示介于U 10000到U 10 FFFF之间的字符。

如果您想自己编写UTF-8处理,请参考以下文章。

  • 从UTF-8字符中找到代码点
  • 从代码点生成UTF-8字符
  • 考虑到无效的字节字符串,从UTF-8字符串中逐字符提取字符

mb_chr

如果将无效的代码点指定为参数,则该函数将作为发出致命错误并停止处理的选项。对于此选项,函数的用户需要知道有效的代码点以防止其停止。世界上有许多不同的字符编码,这需要CMS和框架开发人员的大量知识。

由此可以说,在实现标准功能时,有必要问"使用该功能的最低知识是什么?"。

对于

mb_chr的参数,如果考虑从多个代码点生成字符串而不使用循环的便利性,则第一个参数将接收数组或类似JavaScript fromCodePoint的变量参数。考虑到代码和数据的分离以及具有大量元素的数组的处理,接收数组的方法可能更易于使用。

mb_ord

如果指定了无效的字节字符串,则可以选择返回nullfalse。带有intl的graphme_strlenu选项的preg_matchjson_encode的返回值具有此类规范。这种规范的问题在于,要求用户在进入功能之前处理无效字节。使用稍后描述的mb_convert_encodingmb_scrubUConverter::transcodeUConverter::convert将无效字节字符串替换为替代字符。但是,在不了解字符编码的情况下,如果您不知道什么是坏字节字符串,则不知道如果返回falsenull该怎么办。

在CMS或框架中使用此功能时,需要记住将无效字节字符串转换为备用字符的过程。

从上面的内容中,我没有选择返回nullfalse或遇到无效字节字符串时会导致致命错误的规范。

Go是

函数行为的参考。当Go使用与range组合的for语句从一个字符串中提取字符时,它将无效字节字符串转换为U FFFD。请参阅本文以了解Go行为。 Go的作者Rob Pike也参与了UTF-8的开发。

mb_scrub

据我所知,mb_convert_encoding自PHP 5.2以来就被用来替换坏字节,但这是我第一次编写代码,因为转换前和转换后的字符编码是相同的。观众将不知道他们在做什么。提供函数名称可以使您更轻松地知道自己在做什么。

如前所述,名称mb_scrub取自Ruby的String#scrub。请参考本文以了解开发过程。用例适用于包含由于字符串截断或子字符串检索操作而损坏的字符的情况。

与无效字节字符串有关的安全性问题可以在Tokumaru教授(ockeghem)的幻灯片中找到,"过去三年中解决了多少字符代码漏洞?"(2014年)和"系统学习安全性"。制作一个Web应用程序"(2011,SB Creative)。

未来发行

  • 添加官方PHP手册
  • 建议将字符编码选项添加到标准函数chrord
  • mb_scrub等效的str_scrub提案