31
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPでSJISのデカイCSVデータを扱った時に困ったこと

Last updated at Posted at 2018-05-15
1 / 20

第39回関西PHP勉強会 のスライドです。

(おことわり)
スライド上では全て文字エンコーディングのことを「文字コード」と表記していますので、ご了承ください。


やりたいこと


困ったこと

  • 文字コードが「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 などを正しく処理する
  • テンポラリファイルに書き込む

<ファイルポインタの位置を戻す>

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() or fgetcsv() でCSV読み込み
    • mb_convert_encoding() でSJIS=>UTF-8に変換
    • tmpfile() でテンポラリファイル作成
  • データ件数が約12万件と大きい
    • SplFileObject クラスでforeachを回して処理

文字コードは調べてみるとかなり奥深そうなので、勉強してみる価値がありそう。
CSVファイルをお客さんから渡されることはよくあるし、PHPでのファイルの扱いについてももうちょい詳しくなっておきたい。

31
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?