高速かつ省メモリなCSVダウンロード機能の作り方です。
一時ファイルを生成せずに、標準出力に垂れ流すことで、オーバーヘッドを減らしています。
ただし、headerにContent-Lengthを含めることができないので、ダウンロードまでの残り時間をブラウザに表示させることができなくなります。
データ生成の方法
yieldを使う方法
yieldを使うと、メモリを節約できます。
ただし、こんなふうに直接固定データを返す実装は少なく、多くのケースでは、SQLで、データを取りにいき、その結果をCSVとして返すという実装が多いことと思います。
その場合、問い合わせ回数が増えるので、遅くなる可能性があります。
<?php
function getUsers()
{
yield ['id' => '1', 'name' => '田中一郎'];
yield ['id' => '2', 'name' => '山田二郎'];
yield ['id' => '3', 'name' => '佐藤三郎'];
}
普通に配列を返す方法
この場合は、メモリを大量に消費する可能性があります。
メモリを拡張する必要が生じる可能性もあります。
<?php
function getUsers()
{
return [
['id' => '1', 'name' => '田中一郎'],
['id' => '2', 'name' => '山田二郎'],
['id' => '3', 'name' => '佐藤三郎'],
];
}
CSVダウンロード処理実体
<?php
function download()
{
// ファイルを作成すると遅くなってしまうので、標準出力に流していく
$stream = fopen('php://output', 'w');
// CSVはExcelで開かれるケースが多いので、デフォルトで文字化けしないように、CP932に変換。
// UTF-8のままで良いなら不要です。
stream_filter_append($stream, 'convert.iconv.utf-8/cp932//TRANSLIT');
// ダウンロードを強制する。
header('Content-Type: application/octet-stream');
// ファイル名の指定
header('Content-Disposition: attachment; filename=users.csv');
// ヘッダーだけまずは標準出力に流す。
fputcsv($stream, ['id', 'name']);
// データを標準出力に流す
foreach (getUsers() as $user) {
fputcsv($stream, [$user['id'], $user['name']]);
}
fclose($stream);
}
download();
さらに良い方法があれば、ぜひコメントをお願いします!