ひでメモ

プログラムについて勉強したことを書きます。たぶん。

【Laravel】レスポンスでファイルを返した後にファイルを削除する処理のテスト

Laravel で「ファイルをダウンロードした後はそのファイルは削除」する処理を実装したときにテスト方法にちょっと手間取ったのでメモです。

前提

Laravel では以下のように書けばファイルをダウンロード後、そのファイルを削除してくれます。

return response()
            ->download($file)
            ->deleteFileAfterSend(true);

readouble.com

テストでファイルが削除されない

上記のような値を返すメソッドをテストしたときにファイルが削除されていないことに気づきました。

これはdeleteFileAfterSend メソッドが内部で送信後に削除するフラグをオンしているだけなので、送信処理がないとファイル削除処理が実行されないためです。
この場合返ってくるのはあくまでBinaryFileResponseインスタンスなので、レスポンスを送信する処理自体は行われていません。
※送信処理の後にファイル削除処理が走る流れになっていました

対処方法

以下のように書けばレスポンスの送信処理が走りファイルが削除されました!

public function fileDownload(){
     return response()
            ->download($file)
            ->deleteFileAfterSend(true);
}

// 記事冒頭の上記の値が返ってくるメソッドを呼び出す
$response = $fileDownload->fileDownload();

ob_start();
$response->send();
ob_end_clean();

// この後ファイルが削除されていることを確認する

ob_startob_end_cleansendメソッドの途中にある標準出力へのファイル出力をコンソールなどに表示しないための処理です。
バッファに吐き出してそのまま破棄しています。

もう少しメソッドの中身を追ってみる

実際はもうちょっと Laravel のメソッドの中身を確認したうえで上記の結論にたどり着いてますので、備忘録も兼ねて記載しておきます。 以下のコード(再掲)がどのような処理になっているか少し追ってみました。

return response()
            ->download($file)
            ->deleteFileAfterSend(true);

downloadメソッドはBinaryFileResponseクラスを返します。
続くdeleteFileAfterSendメソッドはBinaryFileResponseクラスのプロパティdeleteFileAfterSendを変更しているようです。(以下)

 public function deleteFileAfterSend($shouldDelete = true)
    {
        $this->deleteFileAfterSend = $shouldDelete;

        return $this;
    }

で、このdeleteFileAfterSendプロパティがどこで使われているかというとsendContentというsendメソッドの中で呼ばれているメソッドでした。
※以下に含まれている日本語コメントはもちろんこちらで追記したものです

    /**
     * Sends the file.
     *
     * {@inheritdoc}
     */
    public function sendContent()
    {
        if (!$this->isSuccessful()) {
            return parent::sendContent();
        }

        if (0 === $this->maxlen) {
            return $this;
        }

// [1] ここでファイルをダウンロードさせる処理をしているようです
        $out = fopen('php://output', 'wb');
        $file = fopen($this->file->getPathname(), 'rb');

        stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);

        fclose($out);
        fclose($file);

// [2] ダウンロード処理が終わったので、deleteFileAfterSend が有効な場合は削除(unlink)しているようです
        if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
            unlink($this->file->getPathname());
        }

        return $this;
    }

[1] でファイルの中身を標準出力に書き出し=ダウンロード処理をして、
[2] でフラグdeleteFileAfterSendがオンならばファイルを削除しています。

どうやって削除しているんだろうと思っていましたが、フラグを立てておいて送信後に削除しているだけでした。 ユーザが処理を意識しないで使えるのは見習いたいと思いました。フレームワークのメソッドはわかりやすくて勉強になりますね〜