現象
Laravel 11 で、ファイルダウンロードを実装したく、 response()->download()
を使った
実装
<?php use App\Http\Controllers\Controller; class HogeController extends Controller public function getCsv(Request $request) { $filepath = '/tmp/hoge.csv'; $filepath = 'hoge.csv'; return response()->download($filepath, $filepath); } }
するとこのようなエラーが発生した
Call to undefined method Symfony\Component\HttpFoundation\BinaryFileResponse::withHeaders()
原因
エラー時のスタックトレースを見てみると、自分が実装した middleware でこのエラーが発生していた。
この middleware では、 $response->withHeaders()
を使って、全てのレスポンスに特定のヘッダーを付与する middleware だったが、
response()->downoload()
で返される Symfony\Component\HttpFoundation\BinaryFileResponse
には withHeaders()
メソッドが存在しないため、middleware でエラーになっていた。
withHeaders() メソッドについて
withHeaders()
メソッドは Illuminate\Http\Response
クラスに実装されているため、一見 middleware を通る全ての Response に対して使えそうだが、特定の場合はそうではない。
今回の response()->downoload()
は Symfony\Component\HttpFoundation\BinaryFileResponse
を返すが、Laravel の response クラスの継承関係は以下のようになっており、 BinaryFileResponse
は Illuminate\Http\Response
を継承していないので、 withHeaders()
は使えない。
Symfony\Component\HttpFoundation\SymfonyResponse
Symfony\Component\HttpFoundation\BinaryFileResponse
Symfony\Component\HttpFoundation\StreamedResponse
Illuminate\Http\Response
このように、いくつかのレスポンス(主にファイルダウンロード周り)は Illuminate\Http\Response
を継承しておらず、
Symfony\Component\HttpFoundation\BinaryFileResponse
response()->download()
response()->file()
Symfony\Component\HttpFoundation\StreamedResponse
response()->stream()
response()->streamDownload()
これらの reponse 系のメソッドを使った場合、 Illuminate\Http\Response
が来ないので注意しておく必要がある。
参考として、Laravel 公式ドキュメントのレスポンスの部分を置いておく
対応
いくつかの方法があるが、一番素直なのは、 Symfony\Component\HttpFoundation\SymfonyResponse
に対応できるようにしておくこと。
$response->withHeaders()
の代わりに、
$response->headers->set('key', 'value')
を使えば、 SymfonyResponse
全体に対応できる。
その他の対応
- middleware で withHeaders する際に
$response instanceof Illuminate\Http\Response
をチェックしておく - Route に
withoutMiddleware
をつけることで、指定した middleware を除外することができるため、 download などのレスポンスを返す場合はこの機能を利用する
しかし、どちらも場当たり的な実装なので推奨はできない。 SymfonyResponse
が来た場合にも対応できるようにしておくほうが、王道な気がする。
特に、withoutMiddleware については、今後新たに middleware を追加した場合に、withoutMiddleware に追加し忘れるリスクがあるため、これを利用するのであれば middleware 適用のルールを根本的に見直した方がよさそう(全てのルートに対して適用させる middleware を可能な限り設定しないようにする、等)。