現象
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\SymfonyResponseSymfony\Component\HttpFoundation\BinaryFileResponseSymfony\Component\HttpFoundation\StreamedResponseIlluminate\Http\Response
このように、いくつかのレスポンス(主にファイルダウンロード周り)は Illuminate\Http\Response を継承しておらず、
Symfony\Component\HttpFoundation\BinaryFileResponseresponse()->download()response()->file()
Symfony\Component\HttpFoundation\StreamedResponseresponse()->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 を可能な限り設定しないようにする、等)。