【PHP】mb_chr と mb_ord のレガシーエンコーディングに関する不適切な仕様
PHP 8.2 で mb_chr
と mb_ord
を調べたところ、レガシーエンコーディングに関する不適切な仕様を見つけました。
mb_chr
の第1引数および mb_ord
の戻り値が Unicode のコードポイントになっており、内部の実装では Unicode とレガシーエンコーディングのあいだの変換処理を含みます。
この仕様は NEC や IBM の機種文字を扱うときに問題になります。例としてほぼ等しいをあらわす近似記号(≒、U+2252)を挙げます。Shift_JIS では2つの定義(0x81E0、0x8790)があります。
// U+2252: Approximately Equal to or the Image Of
var_dump(
"\x81\xE0" === mb_chr(0x2252, 'cp932'),
0x2252 === mb_ord("\x87\x90", 'cp932')
);
mb_chr
で U+2252
を指定すれば \x81\xE0
は生成できますが、\x87\x90
は mb_chr
から直接生成できません。Shift_JIS の文字をすべて生成するテストをする際にはコードポイントから文字を生成する関数を自前で実装しなければなりません。
\x87\x90
から U+2252 のコードポイントを求めることはできますが、前述のとおり mb_chr
で U+2252
から \x87\x90
を生成することはできません。
Shift_JIS の大文字小文字変換や空白文字の除去など、文字とコードポイントのあいだの変換処理を PHP のコードで実装する場合、仕様で想定しない文字まで変換するバグを抱えることになります。
実際、mb_scrub
、mb_strtolower
、mb_strtoupper
、mb_convert_case
は機種文字を変換してしまうバグを抱えています。
var_dump(
"\x81\xE0" === mb_scrub("\x87\x90", 'cp932'),
"\x81\xE0" === mb_strtolower("\x87\x90", 'cp932'),
"\x81\xE0" === mb_strtoupper("\x87\x90", 'cp932'),
"\x81\xE0" === mb_convert_case("\x87\x90", MB_CASE_TITLE, 'cp932')
);
私が mb_chr
、mb_ord
をコミットした時点(2016年)では Unicode との変換を含まない仕様になっていたのですが、知らないうちに仕様が変わっていました。近いうちに PHP の Github の Issue を立てて開発者と協議します。
Discussion