LoginSignup
26
19

More than 3 years have passed since last update.

横長のデータでも Laravel の CSV 出力を行うためには

Last updated at Posted at 2019-05-30

はじめに

CSV を Laravel で出力しようと思ったら、関数一発かませば終わり、とはいきません。
以前利用していた FuelPHP はその辺楽だったんですが、Laravel では横着ができないので
もうちょっとビシッと書く必要があります。

意外と知られてないけど

だいたい CSV って Excel で読ませますよね?
UTF-8 で出力すると Excel で読んだ時に化けるからみなさん一生懸命
Shift-JIS化してると思います。私もそうでした。

ですが BOM をつけてやると UTF-8 のまま出力しても Excel で読めます。
まあ、なんて便利なのでしょう...

間違った書き方

DemoController.php
public function csv()
{
    // リスト
    $lists = [
        ['おはよう', 'おやすみ'],
        ['こんにちは', 'さようなら'],
    ];

    // ファイル生成
    $stream = fopen('php://output', 'w');
    fwrite($stream, pack('C*',0xEF,0xBB,0xBF)); // BOM をつける

    // ヘッダー
    fputcsv($stream, ['header1', 'header2']);

    // 
    foreach ($lists as $list) {
        fputcsv($stream, $list);
    }

    return response(stream_get_contents($stream), 200)
                 ->header('Content-Type', 'text/csv')
                 ->header('Content-Disposition', 'attachment; filename="demo.csv"');
}

何が問題か

これは $header$list のデータサイズが小さければ全く問題なく出力されるんですが、
一行がある程度の長さを越えると CSV ではなく、レスポンスがなぜか0バイトになったり HTTP ヘッダの
Content-Typetext/html になったりします。

php://output の部分を php://tempphp://memory にしても解決しませんでした。

output だとヘッダが代わり、後者の二つだと0バイトという現象でした。
カラム内のデータサイズにもよるんですが、体感では50-80カラムくらい横に伸ばすと
NGになってしまいました。

正しい書き方

自分はまずトレイトを作りました。

App/Http/Traits/Csv.php
namespace App\Http\Traits;

trait Csv {

    /**
     * CSVファイルを生成する
     * @param $filename
     */ 
    public static function createCsv($filename) {
        $csv_file_path = storage_path('app/'.$filename);
        $result = fopen($csv_file_path, 'w');
        if ($result === FALSE) {
            throw new Exception('ファイルの書き込みに失敗しました。');
        } else {
            fwrite($result, pack('C*',0xEF,0xBB,0xBF)); // BOM をつける
        }
        fclose($result);

        return $csv_file_path;
    }

    /**
     * CSVファイルに書き出す
     * @param $filepath
     * @param $records
     */    
    public static function write($filepath, $records) {
        $result = fopen($filepath, 'a');

        // ファイルに書き出し
        fputcsv($result, $records);

        fclose($result);
    }

    /**
     * CSVファイルの削除
     * @param $filename
     */  
    public static function purge($filename) {
        return unlink(storage_path('app/'.$filename));
    }
}

次にコントローラーで利用できるようにします。

DemoController.php

use App\Http\Traits\Csv;

class DemoController extends Controller
{
    use Csv;

    // 略
}

で、method 側はこう

DemoController.php

public function csv() {
    // リスト
    $lists = [
        ['おはよう', 'おやすみ'],
        ['こんにちは', 'さようなら'],
    ];

    $filename = 'demo.csv';
    $file = Csv::createCsv($filename);

    // ヘッダー
    Csv::write($file, ['header1', 'header2']); 


    // 値を入れる
    foreach ($lists as $list) {
        Csv::write($file, $list);
    }

    $response = file_get_contents($file);

    // ストリームに入れたら実ファイルは削除
    Csv::purge($filename);

    return response($response, 200)
             ->header('Content-Type', 'text/csv')
             ->header('Content-Disposition', 'attachment; filename='.$filename);
}

というわけで、stream に全部いれて横着しようとしたらあかんかった、という結論です。
php.ini のパラメータどっかいじればいいんでしょうけど、まあ実際はファイルにちょびちょび書いていったらいいだけでした。

よく考えたら

Laravel感があるのストレージパスとかだけやった。

26
19
1

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
26
19