第39回関西PHP勉強会 のスライドです。
(おことわり)
スライド上では全て文字エンコーディングのことを「文字コード」と表記していますので、ご了承ください。
やりたいこと
- 郵便局が提供している郵便番号一覧データをDBに入れる
困ったこと
-
文字コードが「SJIS」
- 文字コードの変換が必要
-
データ件数が約12万件と大きい
- ファイル操作に工夫が必要
1つめの困りごと:文字コードが「SJIS」
【SJIS(Shift_JIS)】
- 日本語を含む文字列を表現するために用いられる文字コードの一つ
- SJISとSJIS-winがあり、SJIS-winの方が対応文字数が多い(①②、はしご高など)
- ガラケーなどの用いられている
- 扱いにくい...
[参考]
PHPの文字コードではSJISじゃなくてSJIS-win、EUC-JPじゃなくてeucJP-winを
【UTF-8】
- 表示範囲が広く、どの国の文字も文字化けしない
- 世界標準になってきている
- ほぼ全てのPC環境で読める
- これにしたい!!
[参考]
Shift-JISとUTF-8の違い(EUC-JPも)
方法
- CSVデータを読み込む(文字列に変換)
- 文字コードを変換
- テンポラリファイルとして作成
CSVデータを読み込む(文字列に変換)
$data = file_get_contents('database/csv/m_postalcode.csv');
- ファイルの内容を全て文字列に読み込む関数
- ファイルパスを指定してデータ取得するだけでなく、URLを入れてその情報を取ることもできる
文字コードを変換
$data = mb_convert_encoding($data, 'UTF-8', 'SJIS-win');
- 文字コードを変換する関数
- 「SJIS-win」から「UTF-8」に変換
- fromの方を指定しない場合は内部文字エンコーディングが使われる
- これでUTF-8になる
テンポラリファイルを作成
$temp = tmpfile(); //テンポラリファイルの作成
$meta = stream_get_meta_data($temp); //メタデータの取得
fwrite($temp, $data); //ファイル書き込み
rewind($temp); //ファイルポインタの位置を戻す
<テンポラリファイルの作成>
$temp = tmpfile();
- テンポラリファイルを作成する
- 書き込み可のモード(w+)でユニークな名前をもつテンポラリファイルを作成、ファイルハンドルを返す
<メタデータの取得>
$meta = stream_get_meta_data($temp);
- ヘッダーあるいはメタデータをストリームまたはファイルポインタから取得する
- ここで取得したメタデータをデータの挿入時に使う
<ファイル書き込み>
fwrite($temp, $data);
- バイナリセーフなファイル書き込み処理
- バイナリセーフ:NULL
\0
、改行\r\n
などを正しく処理する
- バイナリセーフ:NULL
- テンポラリファイルに書き込む
<ファイルポインタの位置を戻す>
rewind($temp);
- ファイルポインタの位置を元に戻す
- ストリームの先頭に戻す
- UTF-8に変換されたファイルのできあがり!!
2つめの困りごと:データ件数が約12万件と大きい
-
file_get_contents()
だとメモリ不足になる可能性がある- 足りない&文字列で返したい場合は
fgetcsv()
をうまいこと使う - 文字列にする必要がない場合はメモリも食わない
SplFileObject
クラスが便利
- 足りない&文字列で返したい場合は
SplFileObjectクラス
$file = new SplFileObject($meta['uri'], 'rb');
- ファイルをオブジェクト思考っぽく扱えるクラス
- 行単位で読み込んで操作するような処理に便利
- rb:読み込み許可&バイナリモード
-
setFlags
で空行処理などの指定が可能
$file->setFlags(
\SplFileObject::READ_CSV | //CSV列として行読み込み
\SplFileObject::READ_AHEAD | //先読み/巻き戻し
\SplFileObject::SKIP_EMPTY | //空行読み飛ばし
\SplFileObject::DROP_NEW_LINE //行末の改行読み飛ばし
);
$file = new SplFileObject($meta['uri'], 'rb');
$list = [];
foreach ($file as $line) {
$list[] = [
"jp_local_govt_code" => $line[0],
......
];
if (count($list)>1000) {
Postalcode::insert($list);
$list = [];
}
}
if (count($list)) {
Postalcode::insert($list);
}
foreachで回して処理するだけ。
これで大きいデータがDBに入った!!
最終コード
$data = file_get_contents('database/csv/m_postalcode.csv');
$data = mb_convert_encoding($data, 'UTF-8', 'SJIS-win');
$temp = tmpfile();
$meta = stream_get_meta_data($temp);
fwrite($temp, $data);
rewind($temp);
$file = new SplFileObject($meta['uri'], 'rb');
$list = [];
foreach ($file as $line) {
$list[] = [
"jp_local_govt_code" => $line[0],
......
];
if (count($list)>1000) {
Postalcode::insert($list);
$list = [];
}
}
if (count($list)) {
Postalcode::insert($list);
}
まとめ
- 文字コードが「SJIS」
-
file_get_contents()
orfgetcsv()
でCSV読み込み -
mb_convert_encoding()
でSJIS=>UTF-8に変換 -
tmpfile()
でテンポラリファイル作成
-
- データ件数が約12万件と大きい
-
SplFileObject
クラスでforeachを回して処理
-
文字コードは調べてみるとかなり奥深そうなので、勉強してみる価値がありそう。
CSVファイルをお客さんから渡されることはよくあるし、PHPでのファイルの扱いについてももうちょい詳しくなっておきたい。