Zenn
👏

【Laravel】DBをCSV出力してダウンロードする方法(大容量でも早い!)

2022/07/24に公開

はじめに

「CSV出力させる機能が欲しい!」
「出力したCSVをダウンロードできるようにしたい!」
「DBでログを保存しているのでCSVで社内でも共有できるようにしたい!」

実務でも他社とAPIなどで連携している際など、リクエスト内容を見ることができないので、ログをとることがあります。
そういった時、ログをとったもののDBの内容だけではデータが多すぎたり、内容を整形してくれていないので見にくいことや、外部の人に共有できないなど問題点が多くあります。
CSVをダウンロードできる機能をさっと用意してあげると、このような問題を解決できてみんな笑顔になりますね😊

今回はDBの内容をCSVにしてダウンロードする方法をコード付きで解説させていただきます!

対象者

この記事は下記のような人を対象にしています。

  • 駆け出しエンジニア
  • プログラミング初学者
  • DBの内容が見づらいと思っている人
  • なにがなんでもCSVをダウンロードしたい人

結論

以下のようなテーブルで、サービス内の一部の機能を使用したユーザーの情報を、ログとして保存しているとします。
HogePlayerLog

id content created_at updated_at
1 {"text":{"name":"hoge親太郎", "play_count":4}, "is_parent":true} 2022-07-22 17:37:23 2022-07-22 17:37:23
2 {"text":{"name":"hoge子太郎", "play_count":9}, "is_parent":false} 2022-07-22 17:37:23 2022-07-22 17:37:23
// テーブルの中身を配列で取得
$logs = HogePlayerLog::get()->toArray();

// 取得してきたログをCSV閲覧時に見やすいように加工
foreach ($logs as &$log) {
	$log['log']    = '';
	$log['is_parent'] = '';
	// json_encode関数で文字化けを防ぎながら整形
	$log['log'] .= json_encode($log['content']['text'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
	$log['is_parent'] .= $log['content']['is_parent'] ? 'true' : 'false';
	// toArray()によりUTC日時が渡されるため、再度日本時間に変更する
	$log['created_at'] = Date::make($log['created_at'])
		->setTimezone('Asia/Tokyo')
		->format('Y/m/d H:i:s');
	$log['updated_at'] = Date::make($log['updated_at'])
		->setTimezone('Asia/Tokyo')
		->format('Y/m/d H:i:s');
	// logに入れているのでcontentは削除
	unset($log['content']);
}
// 参照渡しのforeachを使用しているのでunsetで参照の解除
unset($log);

// ファイル名:現在日時を入れるとファイル名が被らない
$fileName = 'hoge-log-' . now()->format('YmdHis') . '.csv';
// csvのカラム名を用意
$header   = [
	'id',
	'created_at',
	'updated_at',
	'log',
	'is_parent',
];

// レスポンスにCSVを乗せるためにSymphonyのStreamedResponseを使用
$response = new StreamedResponse();
$response->setCallback(function () use ($header, $logs) {
	// ファイルポインタ(超ざっくり言うと一時保存する場所)を作成
	$outputStream = new \SplFileObject('php://output', 'w');
	// 文字化け対策
	mb_convert_variables('SJIS-win', 'UTF-8', $header);
	// 以下で用意したファイルポインタに書き込んでいく
	// flush()でこまめにブラウザへ送信
	$outputStream->fputcsv($header);
	flush();
	foreach ($logs as $row) {
		mb_convert_variables('SJIS-win', 'UTF-8', $row);
		$outputStream->fputcsv($row);
		flush();
	}
});

// レスポンスの形式を定義(ファイル名やファイルの種類、文字コード)
$response->setCharset('SJIS-win');
$response->headers->set('Content-Disposition', "attachment; filename={$fileName}");
$response->headers->set('Content-Type', 'application/octet-stream');

// 作成したレスポンスを返す
return $response;

コードの流れ

大まかな流れとしては、以下のように4つの項目になります。

  1. クエリを叩いてDBの内容を配列で取得
  2. foreachでDBの内容をCSV用に加工
  3. CSV内のカラム名を用意
  4. CSVにDBの内容を入れてレスポンスに返す

ボタンでダウンロード

先ほどのコードをActionなどに記述して、以下のようにボタンで叩いてあげるとダウンロードすることができます。

const onHogePlayerLogDownloadButtonClicked = () => {
   window.location.href = route('download-hoge-player-log');
}

追加機能:日付指定

現状のコードでは全件を取得しているので、他の人に見せるにはデータが多すぎるという問題があります。
なのでwhereで日付を指定できるようにして、指定した日付だけのログをCSVに出力することができます。
Classの引数に、日付を20220724のような形式でFormなどで渡してあげると、日付指定機能を実装することができます。

// DBの内容を取得している箇所を以下のように修正
// $hogePlayerLogDateはクラスの引数
// フォームに何も入れていないなら全権取得(ここはバリデーションを用意してもよい)
if ($hogePlayerLogDate) {
	$logs = HogePlayerLog::whereDate('created_at', '>=', Date::make($hogePlayerLogDate)
		->format('Y-m-d 00:00:00'))
		->whereDate('created_at', '<=', Date::make($hogePlayerLogDate)
		->format('Y-m-d 23:59:59'))
		->get()
		->toArray();
} else {
	$logs = HogePlayerLog::get()->toArray();
}

追加機能:ログをとったDBのレコードを定期削除

ログをとるテーブルなどを作成した場合は、常に溜まり続けることで負荷が上がってしまいます。
そういった際、定期的に1週間前以降のデータをsqlファイルにし、S3へアップロードしてからレコードを削除する記事を書きましたので下記からご参照ください🙇‍♂

【Laravel】DBのデータを定期的にS3へアーカイブして負荷軽減!!!

おわりに

DBに保存しているログなどをCSV形式でダウンロードする方法についてまとめました。
ネットには遅い方法や、古い記事などが多かったのと、CSVをダウンロードするところまで解説しているサイトが少なかったのでまとめさせていただきました。

絞り込みなどはgetする前にwhereなどを使用すればできるので、クラスの引数に日付を持たせて指定した日付だけのログを取得するなど、アレンジも可能ですね!

まだ記事の作成に慣れていないこともあり、コードの不備や誤字などがありましたら、レビューでそっと教えてくださると嬉しいです!
今後も誰かの業務、学習にちょっとでも助けになるような記事を書いていこうと思います。

最後になりますが、ここまでこの記事を読んでくださりありがとうございました。

Discussion

ログインするとコメントできます