PHPでCSV読み込み関数の決定版を作ってみた。

要件

PHPでCSVを読み込む方法はネット上でたくさん見つかりますが、文字コードの変換のために一旦別ファイルに保存したり、読み込んだデータにアクセスしにくかったりで、何度書いてもなんとなくスッキリしてませんでした。もっといい方法があるんじゃないかなーって。 で、今回満足のいくCSVローダーが書けたので、ご紹介します。

要件は以下の通り。

  • CSVデータ中に " や改行が入っている場合にも正しく対応したい。
  • 変数に読み込まれたデータは文字コードUTF-8にすること。
  • 文字コードの変換の際に一時ファイルを作らないで済むようにしたい。
  • 各レコードの列にアクセスするのに、カラム列番号じゃなくてCSV1行目のヘッダ文字列をキーにしたい。
  • 数万行くらいのデータならさくっと読み込んで欲しい。
  • PHP5.3でも動かしたい。

CSV読み込みPHP関数

ということで、PHPフィルタ(php://filter)SplFileObject を使用してPHP関数を作成しました。

<?php
/**
 * CSVローダー
 *
 * @param string $csvfile CSVファイルパス
 * @param string $mode `sjis` ならShift-JISでカンマ区切り、 `utf16` ならUTF-16LEでタブ区切りのCSVを読む。'utf8'なら文字コード変換しないでカンマ区切り。
 * @return array ヘッダ列をキーとした配列を返す
 */
function get_csv($csvfile, $mode='sjis')
{
    // ファイル存在確認
    if(!file_exists($csvfile)) return false;

    // 文字コードを変換しながら読み込めるようにPHPフィルタを定義
         if($mode === 'sjis')  $filter = 'php://filter/read=convert.iconv.cp932%2Futf-8/resource='.$csvfile;
    else if($mode === 'utf16') $filter = 'php://filter/read=convert.iconv.utf-16%2Futf-8/resource='.$csvfile;
    else if($mode === 'utf8')  $filter = $csvfile;

    // SplFileObject()を使用してCSVロード
    $file = new SplFileObject($filter);
    if($mode === 'utf16') $file->setCsvControl("\t");
    $file->setFlags(
        SplFileObject::READ_CSV |
        SplFileObject::SKIP_EMPTY |
        SplFileObject::READ_AHEAD
    );

    // 各行を処理
    $records = array();
    foreach ($file as $i => $row)
    {
        // 1行目はキーヘッダ行として取り込み
        if($i===0) {
            foreach($row as $j => $col) $colbook[$j] = $col;
            continue;
        }

        // 2行目以降はデータ行として取り込み
        $line = array();
        foreach($colbook as $j=>$col) $line[$colbook[$j]] = @$row[$j];
        $records[] = $line;
    }
    return $records;
}
?>

【更新】2018/1/9 16:12
上記プログラムの26行目に SplFileObject::DROP_NEW_LINE があったのですが、これがあると、複数行入っているセルの1行目の改行コードまで除去されてしまうため、削除しました。

使い方と実行サンプルの画面キャプチャ

以下のように使います。

<?php
$records = get_csv('data.csv');
var_dump($records);
?>

以下、実行サンプルです。

CSV読み込みPHP関数 実行サンプルキャプチャ

このような感じで配列に変換されますので、 $records[0]['名前'] 等で直接データにアクセス出来ますし、この形なら array_multisort()usort() を使って特定のキーでのソートもラクショーですね!

ちなみに、UTF-8版やUTF-16版のCSVを読み込みたい場合は、 get_csv() 関数の第二引数の指定が必須です。(UTF-16版のCSVのみ、区切り文字はカンマ , ではなくタブ \t になります)

$records = get_csv('data.csv'); // Shift-JISのCSV(カンマ区切り)
$records = get_csv('data.csv', 'utf8'); // UTF-8のCSV(カンマ区切り)
$records = get_csv('data.csv', 'utf16'); // UTF-16のCSV(タブ区切り)

また、約3万行のCSVデータ(約2MB)でテストしたところ、2010年発売のMacBookAir(Core2Duo メモリ4GB)上のDockerコンテナ(VirtualBox)上で1.5秒くらいの速度が出てますので、最近の一般的なサーバなら1秒かからないでしょう。とは言ってもサーバプランや回線環境にも依存するものと思いますので、速度が重要なら事前にテストをしてください。

以上、ハッピーCSVライフを!