CakePHP4で動的生成しながらファイルダウンロード

CakePHP4でのファイルのダウンロード方法を解説します。

CakePHP3/4系での記事になります。

通常の方法

まずは、簡単なデータを一括で生成してからダウンロードさせる方法です。

$body = "あいうえお";

$filename = rawurlencode("test.txt");
return $this->response->withType('txt')
       ->withHeader('Content-Disposition', "attachment;filename*=UTF-8''{$filename}")
       ->withStringBody($body);

withTypeメソッドはコンテンツタイプなので、例えばCSVであればcsvを指定します。
後は見ればわかるレベルの簡単なコードかなと思います。

動的生成させる方法

通常の方法で大体は事足りると思いますが、大きなデータをダウンロードさせたい場合はちょっと問題が発生します。
キロバイト程度のサイズならこれでもいいのですが、メガバイト単位のデータサイズになると、PHPで利用可能なメモリ制限の関係上、生成したデータを保持できない可能性が出てきますので、動的に生成して送信させる必要があります。
この場合はCallbackStreamを使ってあげればOKです。
具体的なやり方を記載します。

use Cake\Http\CallbackStream;

//コントローラーの中のアクションの中で

$users = $this->Users->find()
    ->where(['Users.status'=>1]);

//ストリームコールバックを作成
$stream = new CallbackStream(function () use ($users) {  
    foreach ($users as $user) { 
        $data = []; 
        $data[] = $user->id;
        $data[] = $user->name;
        $data[] = $user->email;
        $data[] = $user->created;
        $body = "";
        foreach($data as $val){
            if(!empty($body)){
                $body = $body.",{$val}";
            }else{
                $body = $val;
            }
        }
        echo $body ."\r\n";
    }  
});

// ファイル名
$filename = rawurlencode("users.csv");
return $this->response->withType('csv')
    ->withHeader('Content-Disposition', "attachment;filename*=UTF-8''{$filename}")
    ->withBody($stream);

responseには出力したいデータの代わりに、CallbackStreamオブジェクトを渡してあげます。
CallbackStreamで、少しずつデータを生成して、細切れで出力することで大きなメモリを確保することなく送信が実現できます。
例では、ユーザー一覧のCSVを生成するという想定で、Usersモデルより1レコードずつ読みこんで、カンマ区切りのテキストにして出力する形にしています。
因みに、これだと1レコードずつ送信されるのでやや非効率です。100件とか1000件とかメモリに負荷がかからない範囲で貯めてから出力するようにしたほうがいいかもしれません。

タイトルとURLをコピーしました