[PHP]UTF-8で多言語をファイル出力する際に文字化けする


PHPでUTF-8で中国語をテキストファイルに出力すると、開くアプリケーションによって文字化けが起こる事象に遭遇した。

元のプログラム

<?php

$filePath = 'mojibake.txt';
$value = '你好';

$fh = fopen($filePath, 'w');
fwrite($fh, mb_convert_encoding($value, 'UTF-8'));
fclose($fh);

途中の処理は省略している。

$ cat mojibake.txt
你好%

ちゃんと出力されているし、Macのテキストエディタで開いても正しく表示される。しかしこれをExcelで開くと...

こんな感じで文字化けしてしまう。

原因

アプリケーションによって、Unicodeの符号化方式が判別できず、UTF-8・UTF-16・UTF-32を判別できないため、文字化けが起きてしまう。

解決策

上を判別させるために、ファイル出力時の先頭にBOMをつける。

BOMとは

BOMとは、Byte Order Markの略称で、Unicodeの符号化方式を判別するために、ファイルの先頭につける数バイトのマーク。

プログラムがテキストデータを読み込む時、その先頭の数バイトからそのデータがUnicodeで表現されていること、また符号化形式(エンコーディング)としてどれを使用しているかを判別できるようにしたものである。
バイトオーダーマーク - Wikipedia

BOMは以下の通り。

符号化形式 エンディアンの区別 BOM
UTF-8 0xEF 0xBB 0xBF
UTF-16 BE 0xFE 0xFF
UTF-16 LE 0xFF 0xFE
UTF-32 BE 0x00 0x00 0xFE 0xFF
UTF-32 LE 0xFF 0xFE 0x00 0x00

今回はUTF-8なので、0xEF 0xBB 0xBFを先ほどのファイルの先頭につけるようにする。

修正したプログラム

<?php

$filePath = 'mojibake.txt';
$value = '你好';

$fh = fopen($filePath, 'w');

// add BOM
fwrite($fh, "\xEF\xBB\xBF");

fwrite($fh, mb_convert_encoding($value, 'UTF-8'));
fclose($fh);

Excelで開いても、正しく表示されるようになった。

BOM付かどうかファイル情報を調べる

ファイルにBOMが付いているかはコマンドで調べることができる。

$ file mojibake.txt
mojibake.txt: UTF-8 Unicode (with BOM) text, with no line terminators

BOMが付いているとwith BOMと表示される。

$ file mojibake2.txt
mojibake2.txt: UTF-8 Unicode text, with no line terminators

BOMがない場合は表示されない。