fgetcsv 備忘録
CSVファイルから行ごとに配列へ変換してくれるfgetcsv
ですが、PHP5になってロケール設定を参照するようになり、googleでの検索では他のキーワードとして「fgetcsv 文字化け」「fgetcsv バグ」などが提示されるなどかなり不遇な扱いです。
が、オンラインの関数リファレンスにもこの関数はロケール設定を考慮します。
としっかり注意書きが明記されてるので、裏を返せばサーバのロケール設定でShift_JISを扱えるようにすればExcelで作成したCSVファイルでも問題なく扱えるってことでもあり、多分PHPでCSVファイルを処理する最もスマートな方法ってことでもあります。
ということでロケールにShift_JISの定義を追加してみます(fedoraの場合)。
まずはロケールの確認。
locale -a | grep ja ja_JP ja_JP.eucjp ja_JP.ujis ja_JP.utf8 japanese japanese.euc
ということでロケールにShift_JISは含まれてません。この状態ではPHPでfgetcsv
を使う前に
setlocale('LC_ALL', 'ja-JP.sjis');
としてもロケールが設定できないので、Shift_JISの定義をロケールに追加する必要があります。
sudo localedef -f SHIFT_JIS -i ja_JP ja_JP.SJIS キャラクタマップ `SHIFT_JIS' は ASCII 互換ではありません, ロケールは ISO C に従っていません
なんか怒られますが、実行後もう一度ロケールを確認すると
locale -a | grep ja ja_JP ja_JP.eucjp ja_JP.sjis ja_JP.ujis ja_JP.utf8 japanese japanese.euc
と、ja_JP.sjisが追加されてます。ここまで終えるとサーバの再起動後にShift_JISをロケールに追加完了です。ちなみにPHPでは標準のsetlocale
を使うことでプログラム実行中にロケール情報を設定できるので、Shift_JISの定義をロケールに追加するだけでOKで、サーバのロケールを変更するとかは必要ありません。
とりあえずサンプルとして日本郵便の郵便番号データダウンロードから適当にCSVを貰ってきてテストしてみます。なお、ここにあるCSVファイルは郵便番号や住所が入っているカラムはダブルクォートで括られているのですが、内容がダブルクォートで括られていると問題が起きにくいので、今回のテストでは一旦ダブルクォートは全部消してしまいます。
で、
<?php $file = dirname(__FILE__) .'/26KYOUTO.CSV'; echo "<h1>fgetcsv() TEST</h1>\n"; echo sprintf( "<p>LC_ALL=%s</p>\n", setlocale(LC_ALL, 'ja_JP.sjis') ); if ( file_exists($file) ) { $fh = fopen($file, "r"); $row = 0; while ( ( $data = fgetcsv($fh, 1024, ',') ) !== FALSE ) { $rows++; echo sprintf( "<h2>LINE %d</h2>\n", $rows, $cols ); echo sprintf( "<pre>%s</pre>\n", print_r($data, true) ); } fclose($fh); } else { echo "File not found, $file"; }
これでfgetcsv
利用時に文字化け(全部/一部とも)が起こらなくなるはずです。fgetcsv
もちゃんと設定さえしてやれば巷の評判よりは(きっと)できる子です。
とはいえ、なんでPHP5でfgetcsv
だけがロケール設定を参照するようになったんでしょうね。fgetcsv
のためだけにサーバの設定をおいほれと変えられない状況もありえると思うんですけど。
そういう場合はCSVファイルの内容をロケール設定可能な任意の文字コードに変換した上で一時ファイルへ書込み、その一時ファイルをfgetcsv
に通してやることで対処可能。この方法はPHP5 の fgetcsv() で読み込み内容が腐る現象でも書かれてます。
あるいはPHP5でfgetcsvが正常に動作しないで紹介されている fgetcsv_reg も試してみた限り大丈夫そうです。