LaravelでCSVファイルのインポート・エクスポートをやってみた

前回びくついていた健康診断ですが無事終了しました。加藤です。

最近業務でLaravelを使ったCSVファイルのインポート・エクスポートのプログラムを作成したので、備忘のためにポイントを記事にしようと思います。

インポート

<?php

use Illuminate\Http\Request;


public function import_csv(Request $request)
{
    // アップロードファイルのファイルパスを取得
    $file_path = $request->file('csv')->path();

    // CSV取得
    $file = new \SplFileObject($file_path);
    $file->setFlags(
        \SplFileObject::READ_CSV |     // CSVとして行を読み込み
        \SplFileObject::READ_AHEAD |    // 先読み/巻き戻しで読み込み
        \SplFileObject::SKIP_EMPTY |     // 空行を読み飛ばす
        \SplFileObject::DROP_NEW_LINE    // 行末の改行を読み飛ばす
    );

    // 一行ずつ処理
    foreach($file as $line)
    {
        $data = [
            ‘id’     => $line[0],
            ‘name’   => $line[1],
            ‘sex’    => $line[2],
            ‘message’=> $line[3],
        ];
    }
    
    var_dump($data);
    exit;
}

インポートのポイント

(1)SplFileObjectクラスを使う

LaravelというよりはPHPの話になりますが、SplFileObjectはphpのファイル操作のためのクラスです。 これを使えば、いちいちCSVファイルをfopen()して、ポインタを設定して1行読み込んで、1項目ずつ区切って…ということをしなくてもよく、配列で読み込んでくれるので、データベースに追加したりするのがとてもラクでした。

PHP5.1.0以上なら、SplFileObject::READ_CSVとするのが一番効率が良く、早いようですね。

参考

blog.fenrir-inc.com

(2)実行時間に注意

業務で実装した時にはCSVの中に不正なデータが含まれていないかチェックするため1行読み込みごとにValidatorでチェックをかけていたので、CSVの行数によってはかなり時間かかり、いざテストデータを流し込んでみたらタイムアウトエラーになってしまいました。 (長くなってしまうのでValidatorのプログラムは省略しています)

set_time_limit()で実行時間をデフォルトよりも長めに設定することでタイムアウトは回避できましたが、実動作で想定されるデータ量やサーバのスペック等を考慮しないといけないことを学びました。テスト大事。

お次はエクスポートです。

エクスポート

<?php

use Carbon\Carbon;

public function export_csv($data)
{
    $now = Carbon::now();

    // StreamedResponseの第1引数(コールバック関数)
    $response = new StreamedResponse(function () use ($data) {
        
        // ファイルの書き出しはfopen()
        $stream = fopen('php://output', 'w');

        // ヘッダの設定
        $head = [
            'ID',
            '名前',
            '性別',
            'ひとこと'
        ];

        // 宣言したストリームに対してヘッダを書き出し
        mb_convert_variables('SJIS-win', 'UTF-8', $head);
        fputcsv($stream, $head);
        
        if($data)
        {
            foreach ($data as $line)
            {
                // ストリームに対して1行ごと書き出し
                mb_convert_variables('SJIS-win', 'UTF-8', $line);
                fputcsv($stream, [
                    $line['id'],
                    $line['name'],
                    $line['sex'],
                    $line['message'],
                ]);
            }
        }

        fclose($stream);
    },
        // StreamedResponseの第2引数(レスポンス)
        \Illuminate\Http\Response::HTTP_OK,
        // StreamedResponseの第3引数(レスポンスヘッダ)
        [
            'Content-Type'        => 'text/csv',
            'Content-Disposition' => 'attachment; filename=’.$now->format('YmdHis').'.csv',
      ]
  );

  return $response;
}

エクスポートのポイント

(1)文字コードに注意

このプログラムではmb_convert_variables()で文字コードをSJIS-winで書き出すようにしています。

ダウンロードしたCSVをExcelで開く場合を考えるとSJISが一般的かなという印象ですが、仕様や運用に応じて確認しないといけないポイントだと思いました。

(2)StreamedResponseクラスを使う

StreamedResponseクラスは第1引数にCSVの出力内容をコールバック関数で記述することができます。 上記のプログラムではブラウザから直接ダウンロードするように出力ストリームを宣言して、ヘッダと内容を書き出し、最後にストリームを閉じるところまでを第一引数で指定しています。

第2引数にはレスポンス、第3引数にはレスポンスヘッダを指定しています。

戻り値の$responseをreturnするだけで、手軽にCSVダウンロードを実装することができました。

この形はブラウザからCSVをダウンロードするLaravelのプログラムでは、fopen('php://output', 'w')と一緒によく使われているので、ワンセットで覚えるようにするとよさそうです。

今回は以上です。