てきとうなさいと べぇたばん

非公式 PHP 8.1のmbstringアップグレードガイド

TOP > てきとうにこらむ > ゲーム作りとプログラミング日記 > 非公式 PHP 8.1のmbstringアップグレードガイド

GitHub上のMajor overhaul of mbstringのプルリクエスト

PHP 8.1へのアップグレードにまつわるまとめ

PHP 8.1へのアップグレードには、mbstringにまつわるマニュアルに記述されない後方互換性のない変更が含まれることがあります。そのことを周知するべく、この記事を書くことにしました。

私てきめんは、PHPカンファレンス 2022にて、「治っていくmbstring 令和時代の文字化け」というタイトルでトークしています。以下スライドも参考にしてください。

Major overhaul of mbstringについて

PHP 8.1から、Major overhaul of mbstringと呼ばれる、mbstringの大規模改修の内容が反映されるようになりました。困ったことに、RFC(Request For Comments)やChangelog、マニュアルにない内容で、mbstringを多用するPHPユーザーにとてつもない困惑をもたらすこととなりました。もちろん、バグ修正や高速化などの恩恵も受けられるのですが、後方互換性が失われてはアップグレードを躊躇することになるでしょう。

後方互換性がなさそうな挙動

mbstringはlibmbflというライブラリからなっていて、それをPHPがメンテナンスしているため、全体的にmbstring関数は後方互換性のない変換をする可能性があります。以下は既知の影響になります。

mb_detect_encodingの検出順位が変化した

以下のコードのように、mb_detect_encodingの文字コード検出で挙動が変化します。

var_dump(mb_detect_encoding("繝「繧ク繝舌こ","SJIS-win,UTF-8"));
var_dump(mb_convert_encoding("繝「繧ク繝舌こ", "SJIS", "SJIS,UTF-8"));

「繝「繧ク繝舌こ」とは「モジバケ」をShift_JISにて変換した後、UTF-8でデコードすると出てくる文字です。実は、その文字列はShift_JISでもUTF-8でもvalidな文字列なため、両方を検出することができますが、mb_detect_encodingの順番からするとSJIS-winのほうが優先順位が高いはずです。

しかしながら、このコードを実行すると、8.0と8.1より新しいPHPでは違う結果となります。これは別にShift_JISとかUTF-8とか関係なしに、いろいろな文字コードで検出アルゴリズムが変わっています。

そもそも、mb_detect_encodingの「detect」は、detectと言えるほど検出精度がよくなく、「guess」と言うほどに留めるべきであったと思われます。文字コードの自動判別は限界があるということです。

mb_detect_encodingを使うのがよくないのならば、何を使うべきかといえば「mb_check_encoding」を利用して「自分が想定している文字コードか」を確認すべきでしょう。

また、セキュリティ対策の必読本である、徳丸本では「文字コードの自動検出に頼るのはよくない」などと書かれていますよね?

関連するIssue

mb_convert_encodingによる、違う文字への変換

以下のコードのように、文字コードを変更する関数、mb_convert_encoding関数によって、違う文字に変換される可能性があります。

mb_convert_encoding("~", "UTF-8", "SJIS");

このケースでは、PHP 8.1.7まで「~」が返ってきます。これは、JIS X 0201からすると正しい変換なのですが、PHP 8.0以前では「~」が返ってきます。このように、後方互換性が考えられていないため、いきなり変換されてびっくりすることでしょう。このことを指摘したため、後方互換性が重視されて「~」にPHP 8.1.8以降戻されることとなりました。個人的に、こういうのはRFCを挟んでほしいです。

mb_substitute_characterが突如現れることがある

以下のコードを見てください。

mb_convert_encoding("a", "UTF-8", "UTF-16");

PHP 8.0までは「」(空文字)が返ってきますが、PHP 8.1からは「?」が返ってきます。これは、mb_substitute_characterの設定を反映していることになります。たしかに、$from_encodingにUTF-16を設定していますから、"a"は不正な文字です。しかし、PHP 8.0まではなぜか空文字を返していて、mb_substitute_characterの設定を無視していたようです。PHP 8.1ではmb_substitute_characterの設定の影響を受けることとなります。しかし、これがUTF-16限定の話なのか、他の文字エンコーディングにも及んでいるのかは調査中です。

mb_strwidthの数え方が変わった

以下のコードのような、ZWJ(Zero Width Joinner)などを挟んだ文字の場合に、数え方が修正されました。

mb_strwidth("👨‍👩‍👦", "UTF-8");

半角文字を1、全角文字を2とする関数ですが、PHP 8.0以前では5とカウントされるのに、PHP 8.1以降では8とカウントされます。(3v4l: https://3v4l.org/g7eg6)これは、Unicode 11.0からUnicode 13.0への変換テーブルのアップデートが行われたことによるもののようです。mb_strwidthのほか、mb_strimwidth関数にも影響があります。

mb_strimwidth("👨‍👩‍👦‍👦", 1, 2);

https://3v4l.org/gJTeeによると、やはり切り出され方が違っています。このように、半角と全角の概念を使ったmbstring関数の使用に注意してください。(そもそも、半角・全角という概念を用いた数え方が妥当なのかどうかが疑問に感じますが:例えば「○」は1とカウントされますが、日本人ならばおかしいと感じるでしょう)

SJIS-2004(Shift_JIS-2004)のエンコーディングが治っている

これはext/mbstring/tests/sjis2004_encoding.phptを、PHP 8.0に移植してテストしたときにおこった内容です。とある特定の文字を文字のエンコーディングのチェック(mb_check_encodingなど)をかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合はこちら

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("82ad9951"), // く儔
        "UTF-8", 
        "SJIS-2004"
    ),
    mb_check_encoding($bin, "SJIS-2004")
);

82adも9951もSJIS-2004では正しい文字なのにも関わらず、PHP 8.0では何故かこの2つの文字を組み合わせるとmb_check_encodingでfalseを返していました。それがPHP 8.1で治っていて、trueを返すようになります。

SJIS-mac(MacJapanese)のエンコーディングが治っている

これもext/mbstring/tests/sjismac_encoding.phptを、PHP 8.0に移植してテストしたときに起こった内容です。とある特定の文字をmb_check_encodingなどでかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合はこちら

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("8bcb9b46e6fc9bac82998f818aca816287b38aa9"), // 桐妲蹊岻y潤缶|㌦勧
        "UTF-8", 
        "SJIS-mac"
    ),
    mb_check_encoding($bin, "SJIS-mac")
);

hex2bin("8bcb9b46e6fc9bac82998f818aca816287b38aa9")はSJIS-2004では正しい文字なのにも関わらず、PHP 8.0では何故かこの2つの文字を組み合わせるとmb_check_encodingでfalseを返していました。それがPHP 8.1で治っていて、trueを返すようになります。

SJIS-macにて特殊なローマ数字を当てると不正なエンコーディングとなる

これはちょっとまだよくわかっていないのですが、SJIS-macにて以下のコードでPHP 8.0とPHP 8.1とで違う挙動をします。

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("f8620058004900490049"),
        "UTF-8", 
        "SJIS-mac"
    ),
    mb_check_encoding($bin, "SJIS-mac")
);

ローマ数字である0x85abはSJIS-macでは13を示すXIIIなのです。これを0xf8620058004900490049としてマッピングすることができるということのようなのですが、トランスエンコーディングの処理が間違っていたようだったために起こった変更のようです。(ごめんなさい、MacJapaneseがよくわかってないのでわかってない)

出典: Major overhaul of mbstringのUnicode -> SJIS-mac conversion doesn't reject valid codepoints after a bad transcoding hint

0x5c(バックスラッシュ)をUTF-8などにエンコードすると、0xc2a5(円記号)に変換される

おそらく、バックスラッシュを円記号として解釈するということかと思います。これは正しい変換方法ですが、後方互換性が破壊されているので気をつけてください。(これはIssue行きか?)

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("5c") . "\\",
        "UTF-8", 
        "SJIS"
    ),
    mb_check_encoding($bin, "SJIS"),
    bin2hex($bin)
);

UTF-32のBOMが削除される

これは主にUTF-16やUCS-4への一貫性への対処とあります。出典: Leading BOM is stripped for UTF-32

var_dump(
    mb_convert_encoding(
        $bin = "\x00\x00\xfe\xff\x00\x11\x00\x00",
        "UCS-4BE", 
        "UTF-32"
    ),
    mb_check_encoding($bin, "UCS-4BE")
);

UCS-2のBOMが削除される

これは主にUTF-16、UTF-32、UCS-4への一貫性への対処とあります。出典: Enhance mbstring support for UCS-2 text

var_dump(
    mb_convert_encoding(
        $bin = "\x00\x00\xfe\xff\x00\x11\x00\x00",
        "UCS-2", 
        "UTF-32"
    ),
    mb_check_encoding($bin, "UCS-2")
);

CP932の文字エンコーディングの対応強化

元のコミットがEnhance handling of CP932 text encodingとなっているので、対応強化と書きましたが、CP932の文字エンコーディングの処理が治っています。

また、制御文字が混ざることを許可しないようにもなったようです(コミットメッセージのコメントより)

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("ed40"),
        "UTF-8", 
        "CP932"
    ),
    mb_check_encoding($bin, "CP932")
);

3v4lで確認したい場合にはhttps://3v4l.org/nS7vGへ。

SJIS-Mobile (DoCoMo、KDDI、Softbank)のUnicode <-> SJIS-Mobileマッピングの変更

出典: Fix mbstring support for SJIS-Mobile (DoCoMo, KDDI, and Softbank variants of Shift-JIS)

SJIS-MobileのUnicodeとのマッピングが変更されているようです。以下のコードから違いがわかるかと思います。

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("9dc8f1d78bc795afe3e0f844e0c0eef08d4df349"),
        "UTF-8", 
        "SJIS-Mobile#DOCOMO"
    ),
    mb_check_encoding($bin, "SJIS-Mobile#DOCOMO")
);

3v4lhttps://3v4l.org/uJKcUを見ると分かる通り、UnicodeとSJIS-Mobileとのマッピングが変わっています。SJIS-MobileからUnicodeへ変換するときに、違う文字に変換されている可能性があります。

CP51932(EUC-JPのマイクロソフトのWindows-31Jの互換表現)のエンコーディングが治っている

ext/mbstring/tests/cp51932_encoding.phptをPHP 8.0に移植してテストをしたときに起こった内容です。CP51932にて、とある特定の文字をmb_check_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    mb_convert_encoding(
        $bin = hex2bin("d0d1ecb5dac2adfb4cd4ede7f4"),
        "UTF-8", 
        "CP51932"
    ),
    mb_check_encoding($bin, "CP51932")
);

hex2bin("d0d1ecb5dac2adfb4cd4ede7f4")はCP51932では正しい文字なのにも関わらず、PHP 8.0ではなぜかmb_check_encodingではfalseを返していました。PHP 8.1では治っていて、trueを返すようになります。

CP50220(ISO-2022-JPのマイクロソフトのWindows-31Jの互換表現)のエンコーディングが治っている

ext/mbstring/tests/cp5022x_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。CP50220にて、とある特定の文字をmb_check_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("1b284200"),
        "UTF-8", 
        "CP50220"
    ),
    mb_check_encoding($bin, "CP50220"),
    bin2hex(mb_convert_encoding($conv, "CP50220", "UTF-8"))
);

UTF7-IMAPのエンコーディングが治っている

ext/mbstring/tests/utf7imap_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。UTF7-IMAPにて、とある特定の文字をmb_check_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("00"),
        "UTF-8", 
        "UTF7-IMAP"
    ),
    mb_check_encoding($bin, "UTF7-IMAP"),
    bin2hex(mb_convert_encoding($conv, "UTF7-IMAP", "UTF-8"))
);

これは今までと違い、不正な文字列を不正な文字とチェックすることができるようになったということでしょうか。

ISO-2022-JP-KDDIのエンコーディングが治っている

ext/mbstring/tests/iso2022jp_kddi_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。ISO-2022-JP-KDDIにて、とある特定の文字をmb_check_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("1b284200"),
        "UTF-8", 
        "ISO-2022-JP-KDDI"
    ),
    mb_check_encoding($bin, "ISO-2022-JP-KDDI"),
    bin2hex(mb_convert_encoding($conv, "ISO-2022-JP-KDDI", "UTF-8"))
);

ISO-2022-JP-MSのエンコーディングが治っている

ext/mbstring/tests/iso2022jp_ms_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。ISO-2022-JP-KDDIにて、とある特定の文字をmb_check_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("1b284200"),
        "UTF-8", 
        "ISO-2022-JP-MS"
    ),
    mb_check_encoding($bin, "ISO-2022-JP-MS"),
    bin2hex(mb_convert_encoding($conv, "ISO-2022-JP-MS", "UTF-8"))
);

UTF-16からUTF-32へのエンコーディングが変化している

ext/mbstring/tests/utf_encodings.phptをPHP 8.0に移植してテストしたときに起こった内容です。何故かはまだわかっていませんが、UTF-16からUTF-32へmb_convert_encodingすると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合にはこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("dc01d8020042"),
        "UTF-32", 
        "UTF-16"
    ),
    mb_check_encoding($bin, "UTF-32"),
    bin2hex(mb_convert_encoding($conv, "UTF-16", "UTF-32"))
);

UTF-8はパスできたのですが、UTF-16はパスできなかったため、UTF-7とUTF-32のエンコーディングが変化している可能性があります。ただし、いずれも不正な文字での検証になっていると思われます。

SJIS-openがCP932にまとめられた

SJIS-openとは、suikawikiによると、UI-OSF 日本語環境実装規約版シフトJISとありますが、それがCP932にまとめられました。

出典: Remove duplicate implementation of CP932 from mbstring

ただ、「95-104区は EUC の JIS X 0208 の85-94区に対応付けられていました。」「105-114区は EUC の JIS X 0212 の95-94区に対応付けられていました。」「115-120区は IBM 拡張文字でした。」の部分がちょっと謎に思いました。

OSF 日本ベンダ協議会 (OSF/JVC) 推奨 日本語 EUC ・シフト JIS 間コード変換仕様とコード系実態調査(Internet Archiveより)

UHC(韓国のEUC-KRを拡張し、すべてのハングルを扱えるようにした文字コード)のエンコーディングが治っている

ext/mbstring/tests/uhc_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。UHCと呼ばれる、韓国の文字コード(初めて知りました)にて、とある特定の文字をmb_convert_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("febb90"),
        "UTF-8", 
        "UHC"
    ),
    mb_check_encoding($bin, "UHC"),
    bin2hex(mb_convert_encoding($conv, "UHC", "UTF-8"))
);

変換元であるfeは不正な文字、bb90は퍙という文字ですが、これを正しく処理できるようにした、ということのようです。韓国語詳しくないのでなんか間違っていたらごめんなさい

参考: https://www.wdic.org/w/WDIC/UHC

HZ(中国語の文字コード)のエンコーディングが変化している(混乱注意)

ext/mbstring/tests/hz_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。HZと呼ばれる、中国の文字コード(初めて知りました)にて、とある特定の文字をmb_convert_encodingにかけると、内容が変化します。

例えば、以下のようなコードになります。3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("7e0a"),
        "HZ",
        "ASCII"
    ),
    mb_check_encoding($bin, "ASCII"),
    bin2hex(mb_convert_encoding($conv, "ASCII", "HZ"))
);

これの不思議な点は、PHP 8.1では~\nが再変換されて~に戻るところ、PHP 8.2では~\nにPHP 8.0と同じ内容になっているところです。HZについて説明しているページによると、~\nはASCIIモード中で、行継続となっています。どうやら間違いだったのか、PHP 8.2ではそれが再修正されたようです。参考: Fix legacy conversion filter for HZ (Major overhaul of mbstring part22(PHP 8.2で含まれる内容)より

CP936(中国の文字エンコーディング)にて不正な文字の際の処理が変化している

ext/mbstring/tests/cp936_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。何故か不正な文字である0x3fを使わず、何かおかしい文字に変換されるという謎の挙動を起こしていたのを修正したようです

例えば、以下のようなコードになります。3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("fe7ff9cb"),
        "UTF-8",
        "CP936"
    ),
    mb_check_encoding($bin, "CP936"),
    bin2hex(mb_convert_encoding($conv, "CP936", "UTF-8"))
);

この結果を見ていただくと分かる通り、何故か全く違う内容になっています。それぞれ、mb_check_encodingではfalseを返しますから、コーナーケースであることは間違いないようですが…不思議です。

EUC-KRのエンコーディングが治っている

ext/mbstring/tests/euc_kr_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_convert_encodingにて変化する内容があります。

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("feb6b5"),
        "UTF-8",
        "EUC-KR"
    ),
    mb_check_encoding($bin, "EUC-KR"),
    bin2hex(mb_convert_encoding($conv, "EUC-KR", "UTF-8"))
);

これを見る限り、不正な文字で処理を止めているという感じだったのが、可能な限り正しく変換するようになったと見るべきでしょうか。

EUC-CNのエンコーディングが変化している

ext/mbstring/tests/euc_cn_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_convert_encodingにて変化する内容があります。

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("f2dca7a2a1d7b1a3f7c4a3b0dabddfd2a1a4bbdb"),
        "UTF-8",
        "EUC-CN"
    ),
    mb_check_encoding($bin, "EUC-CN"),
    bin2hex(mb_convert_encoding($conv, "EUC-CN", "UTF-8"))
);

"蜍Б∽保髂0诮咭·慧"だったのが、"蜍Б∽保髂0诮咭・慧"に変わっていますね。これが規格通りかどうかは不明です。すみません…中国語がわからないばかりに…

EUC-TWのエンコーディングが治っている

ext/mbstring/test/euc_tw_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_check_encodingにて変化する内容があります。

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("f9c5f4f4edd4f6cf8eaec3e98ea1eccaddcf8ea2bdb2"),
        "UTF-8",
        "EUC-TW"
    ),
    mb_check_encoding($bin, "EUC-TW"),
    bin2hex(mb_convert_encoding($conv, "EUC-TW", "UTF-8"))
);

何故か変換できない文字があったようです。それが修正されているのでmb_check_encodingでtrueを返すようになったという感じでしょうか。

EUC-JP-2004のエンコーディングが治っている

ext/mbstring/test/eucjp_2004_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_check_encodingにて変化する内容があります。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("8ffbce8fefc5a5c88fafaca6fedcf7"),
        "UTF-8",
        "EUC-JP-2004"
    ),
    mb_check_encoding($bin, "EUC-JP-2004"),
    bin2hex(mb_convert_encoding($conv, "EUC-JP-2004", "UTF-8"))
);

これは正しいエンコーディングのときにきちんとtrueが返すように修正されたものだと思われます。

ISO-2022-KRのエンコーディングに変化がある

ext/mbstring/tests/iso2022kr_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_check_encodingでfalseだったものがtrueになったり、mb_convert_encodingにて変化する内容があります。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("00"),
        "UTF-8",
        "ISO-2022-KR"
    ),
    mb_check_encoding($bin, "ISO-2022-KR"),
    bin2hex(mb_convert_encoding($conv, "ISO-2022-KR", "UTF-8"))
);

これはPHP 8.0まではmb_check_encodingはfalseを返しますが、PHP 8.1からはtrueを返します。しかし、PHP 8.2ではmb_convert_encodingの内容が変化しています。PHP 8.2では Fix legacy conversion filter for ISO-2022-KR (Major overhaul of mbstring (part 22)にて修正されているようです。

CP950のエンコーディングが治っている

ext/mbstring/tests/cp950_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_check_encodingでfalseだったものがtrueになりました。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("9748e3b582e2e1ee9cbff9fcf0b08dc3db4e"),
        "UTF-8",
        "CP950"
    ),
    mb_check_encoding($bin, "CP950"),
    bin2hex(mb_convert_encoding($conv, "CP950", "UTF-8"))
);

BIG5のエンコーディングが変化している

ext/mbstring/tests/big5_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_convert_encodingにて変換する文字が変化しています。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("e4d4bd67c565d8fdf8d5b162c7f8b7acca43"),
        "UTF-8",
        "BIG5"
    ),
    mb_check_encoding($bin, "BIG5"),
    bin2hex(mb_convert_encoding($conv, "BIG5", "UTF-8"))
);

BIG5からUnicodeへ変換する際の文字の内容が変わっているようですね。PHP 8.0までの「」は、U+F5A5であり、PRIVATE USE AREAですね。なぜこのような変換になっていたのでしょうか。

GB18030のエンコーディングが治っている

ext/mbstring/tests/gb18030_encoding.phptをPHP 8.0に移植してテストした際に起こった内容です。mb_convert_encodingにて変換する文字が変化しています。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("ffb383"),
        "UTF-8",
        "GB18030"
    ),
    mb_check_encoding($bin, "GB18030"),
    bin2hex(mb_convert_encoding($conv, "GB18030", "UTF-8"))
);

これは一体どういうことかというと、先頭の0xffはGB18030に収録されていない文字となります。しかしながら、どういうわけかPHP 8.0以前では0xffにISO-8859-1の「ÿ」が割り当てられていたということのようです。参考: 簡体字中国語 (GB18030) 文字コード表

mb_substitute_character("long")の挙動の変更

mb_substitute_character("long")は、文字コードの値を出力する (例: U+3000、JIS+7E7E) となっています。この挙動が変わっている場合があります。

3v4lで確認したい場合はこちら

mb_substitute_character("long");

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("7FFFFFFF"),
        "UTF-8",
        "CP932"
    ),
    mb_check_encoding($bin, "CP932"),
    bin2hex(mb_convert_encoding($conv, "CP932", "UTF-8"))
);

UCS-4BEの場合

mb_substitute_character("long");

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("70000000"),
        "UTF-8",
        "UCS-4BE"
    ),
    mb_check_encoding($bin, "UCS-4BE"),
    bin2hex(mb_convert_encoding($conv, "UCS-4BE", "UTF-8"))
);

3v4lで確認したい場合はこちら

UCS-4BEの場合はちゃんと動くように修正されているようですね。謎が深まるばかりです。

Major overhaul of mbstring (part 11)によると、mb_substitute_characterのBAD+とかそういうのって使わなくない?という議論があったようで、それによるとすべて単一の不正値にまとめられた(-1)とあります。

EUC-JP-MS(eucJP-ms)やEUC-JP-WIN(eucJP-win)のエンコーディングが治っている

ext/mbstring/tests/eucjp_ms_encoding.phptをPHP 8.0に移植してテストしたときに起こった内容です。mb_check_encodingの結果に変化があります。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("a3f8f3abc0ce8ffaa1bce3adfcf1b2"),
        "UTF-8",
        "eucJP-win"
    ),
    mb_check_encoding($bin, "eucJP-win"),
    bin2hex(mb_convert_encoding($conv, "eucJP-win", "UTF-8"))
);

UTF-8-KDDI-A、UTF-8-Mobile#KDDI-B、UTF-8-Mobile#SOFTBANKのエンコーディングが治っている

ext/mbstring/tests/utf8_mobile_encodings.phptをPHP 8.0に移植してテストしたときに起こった内容です。mb_convert_encodingの内容に変化があります。

3v4lで確認したい場合はこちら

var_dump(
    $conv = mb_convert_encoding(
        $bin = hex2bin("0001f1e8"),
        "UTF-8-Mobile#KDDI-A",
        "UCS-4BE"
    ),
    mb_check_encoding($bin, "UTF-8-Mobile#KDDI-A"),
    bin2hex(mb_convert_encoding($conv, "UTF-8-Mobile#KDDI-A", "UTF-8"))
);

UTF-8-Mobile#KDDI-B、UTF-8-Mobile#SOFTBANKでも同じくmb_convert_encodingの内容に変化がありました。

PHP 8.2の注意点

mb_convert_kanaで$flagsにmやM(マニュアル記載外)が使えなくなった

何をするのか謎ですが、気をつけておきましょう。

var_dump(
    mb_convert_encoding(
        mb_convert_kana(mb_convert_encoding("アイウエオ", "JIS", "UTF-8"), "mM", "JIS"),
        "UTF-8",
        "JIS"
    )
);

このようなエラーで止まります。

Fatal error: Uncaught ValueError: mb_convert_kana(): Argument #2 ($mode) must not combine 'M' and 'm' flags in /in/XvIUI:4
Stack trace:
#0 /in/XvIUI(4): mb_convert_kana('\e$B\x001\x002\x003\x004\x005\e(...', 'mM', 'JIS')
#1 {main}
  thrown in /in/XvIUI on line 4

3v4lで確認したい場合はこちら

mb_convert_encodingでBase64、QPrint、HTML、UuencodeがDeprecatedになった

これらをお使いの場合は、それぞれの関数に置き換えるなどの準備をしましょう。

mb_convert_encoding("あいうえお", "Base64", "UTF-8");
mb_convert_encoding("あいうえお", "QPrint", "UTF-8");
mb_convert_encoding("あいうえお", "HTML", "UTF-8");
mb_convert_encoding("あいうえお", "Uuencode", "UTF-8");
Deprecated: mb_convert_encoding(): Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead in /in/8v8TQ on line 2

Deprecated: mb_convert_encoding(): Handling QPrint via mbstring is deprecated; use quoted_printable_encode/quoted_printable_decode instead in /in/8v8TQ on line 3

Deprecated: mb_convert_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead in /in/8v8TQ on line 4

Deprecated: mb_convert_encoding(): Handling Uuencode via mbstring is deprecated; use convert_uuencode/convert_uudecode instead in /in/8v8TQ on line 5

3v4lで確認したい場合はこちら

調査はしましたが…

まだ良くわかっていないMajor overhaul of mbstringですが、この問題に対してどういう変更が入っているのかというのを理解していく必要があります。もしみつけたらphp-srcへIssueを投げてもらえると皆が助かります。ドキュメントに問題があったらドキュメントにIssueを投げてもらえると助かります。

2022/10/12 23:36