Laravel 5.7でCSVをダウンロードするときに内容が画面に表示されてしまう
Laravel 5.7でCSVをダウンロードするときに内容が画面に表示されてしまう
CSVファイルをダウンロードする機能の作成を依頼していて、
質問されたが原因が全然分からなかった。
環境
現象
一定までのサイズならCSVファイルがダウンロードされるが、
一定のサイズを超えると内容が画面に表示される。
とりあえずメモリリークしてね?とは思ったが、今回の問題には関係ないので触れないでおいた。
問題のコード
public function csvDownload(Request $request) { $headers = [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="users.csv"' ]; $stream = csvDownload($request); // 検索してstreamに書き込む return \Response::make(stream_get_contents($stream), 200, $headers); }
原因
下記の参考ページを発見して知ったが、こんな仕様があるらしい。
最後に、Httpヘッダーの作成までに時間がたくさんかかってしまった場合、そもそもファイルのダウンロードに失敗してしまいます。
これは、ブラウザがしばらくレスポンスを待ってもHttpヘッダーが返ってこない場合、デフォルトのヘッダーを使ってブラウザにデータを吐き出してしまうという仕様によるものです。
対応
参考ページに乗っているコードを一部改変し使用した。
変更点
- BOMの付け方が元のやり方では文字列として出力されるため変更
- ネストが浅くなるのでchunkからcursorに変更
use \Symfony\Component\HttpFoundation\StreamedResponse; public function csvDownload(Request $request) { $headers = [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="users.csv"' ]; return new StreamedResponse( function () { $stream = fopen('php://output', 'w'); // ExcelでUTF-8と認識させるためにBOMを付ける(変更部分) fwrite($stream, pack('C*', 0xEF, 0xBB, 0xBF)); // chunkではなくcursorを使用(変更部分) $cursor = \DB::table("users")->orderBy("id")->cursor(); foreach ($cursor as $user) { fputcsv($stream, [$user->id, $user->name]); } fclose($stream); }, 200, $headers ); }