質問: **前提** ・preg_replaceでマルチバイトを扱っているのですが、「文字化けする場合」と「文字化けしない場合」があります ・何れもutf-8ですが、違いが生じる理由が不明 --- **試したこと** ・パターン修飾子uを付与したら文字化け解消されました --- **質問** ・preg_replaceでマルチバイトを扱う際は、常にパターン修飾子uを付与した方が良いですか? ・「文字化けしない」のに、パターン修飾子uを付与して不具合が発生することはない?? --- **A.(修飾子uを付与しないと)文字化けする例** ``` echo preg_replace("/[a-z0-9]*/", "○","あ"); ``` --- **B.文字化けしない例** ``` echo preg_replace("/^(aa)$/i", "○","あ"); ```
回答: ***・preg_replaceでマルチバイトを扱う際は、常にパターン修飾子uを付与した方が良いですか?*** PCRE正規表現は`u`修飾がない場合、文字列を単なるバイト列として処理を行います。したがって、あなたの目的が「バイト列の操作」だと言うのでない限り、UTF-8の文字列を操作する場合には、必ず`u`を付けるべきです。 UTF-8以外のマルチバイトエンコーディングについては、「バイト列の操作」ではなく「文字」の列として扱いたい場合、PCRE系の関数は使えません。 ***・「文字化けしない」のに、パターン修飾子uを付与して不具合が発生することはない??*** あなたが「文字化けしない」と表現する条件も、「不具合」が何を意味するのかも不明確ですのでなんとも言えません。上に挙げたように「バイト列の操作」をすることが目的の場合、1バイト単位で処理できなくなることは「不具合」になるでしょう。 バイト列の操作ではなく、「文字単位」(厳密なことを言うと何が1つの「文字」なんだ?と言うのが出てきますが、ここでは割愛します)の操作として正規表現を使うのであれば、UTF-8文字列に対しては`u`を指定して、一般的に「不具合」と言われるような結果になることはないはずです。 もう少し具体的な条件や例を示していただければ、もっと突っ込んだ話もできるでしょう。 では、ご提示いただいた具体例について内部で何が起こっているのかを見ていきます。 ### A. ``` preg_replace("/[a-z0-9]*/", "○","あ"); ``` 置換先のデータである`"○"`は、内部的に`"\xE2\x97\x8B"`の3バイトのデータとなっています。 入力となっている`"あ"`は、`"\xE3\x81\x82"`です。 パターンの方は、メタ文字以外を文字コードで解釈するとわかりやすくなるかと思います。 ``` "/[\x61-\x7a\x30-\x39]*/" ``` このパターンはバイト値が0x61から0x7Aまたは0x30から0x39であるバイトの **0個以上の並び**(*)にマッチするパターンとなっています。 その結果、置換後のバイト列はこんなものになってしまいます。 ``` "\xE2\x97\x8B\xE3\xE2\x97\x8B\x81\xE2\x97\x8B\x82\xE2\x97\x8B" <-- --> <-- --> <-- --> <-- --> ``` (`<-- -->`で表しているのが0個以上の列にマッチして「置き換えられた」部分です。) UTF-8として見ると、4バイトめからの`\xE3\xE2...`の部分は正しいUTF-8表現とはなっていませんので、そこ(から後)が文字化けすることになります。 ※一度変なバイト(列)が現れた時にどこから再解釈してくれるかは処理系により異なるので、例えばターミナルで見るのとエディターで見るのとでは化け方が違って見えるかもしれません。 ### B. ``` preg_replace("/^(aa)$/i", "○","あ") ``` 入力と置換先は同じですね。パターンはこう解釈できます。 ``` `"/^(\x61\x61)$/i" ``` `i`が指定されているので話がちょっとややこしくなりますが、「文字列の先頭」「0x41(`A`)または0x61」「0x41または0x61」「文字列の終了」と言うパターンですから、入力テキストにはマッチせず、置換も起こりません。当然結果は入力テキストのまま`"\xE3\x81\x82"`(`"あ"`)となり、文字化けは発生しません。 (*) 置換用のパターンとして、挙げられた例のように **0個以上の並び** を表す`*`を指定すると今回のように思わぬ(もしかして意図的?)結果になることがあります。 ``` echo preg_replace("/[a-z0-9]*/", "XYZ","ABC"); //->XYZAXYZBXYZCXYZ ``` UTF-8の場合、非ASCIIの文字を表すバイト列には0x00から0x7Fの範囲が現れないので、パターンにASCII文字だけを使っている場合、「バイト列として処理することの危険性」を示す例を見つけるのが面倒だったりするんですが、なかなか良い例を見つけていただいたようです。