EC-CUBE 4.0のカスタマイズを4.2 / 4.3(Symfony 6)へ移植すると、受注CSVや会員CSVのダウンロードが「1行目のヘッダだけ正しく、2行目以降がHTMLのエラーページ」というファイルになることがあります。原因は StreamedResponse のコールバック内でセッションに触っているコードで、Symfony 6ではレスポンス送出前にセッションが閉じられるため Failed to start the session because headers have already been sent になります。対処はコントローラ内で $response->send() を呼んでから返すことで、4.2以降の公式コードも同じ書き方になっています。
症状の見分け方
ダウンロードしたCSVをテキストエディタで開くと、1行目はヘッダ行、2行目以降が <!DOCTYPE html> から始まるエラーページになっています。HTTPステータスは200で、ブラウザのダウンロードも成功するので、ファイルを開くまで気付きません。var/log/prod/site.log にはこう出ます。
Uncaught Exception: Failed to start the session because headers have already been sent by "/path/to/eccube/vendor/symfony/http-foundation/Response.php" at line 386.
ヘッダ行が出ているのは、CSVサービスがヘッダを書き出したあと、明細を取りに行くところで初めてセッションを読むからです。4.0では同じコードが問題なく動いていたので、移植直後はCSVの処理を疑いにくく、「ダウンロードは成功したのにファイルの後半がエラーページになっている」という症状から調べ始めることになります。
Symfony 6でコールバックの実行タイミングが変わった
StreamedResponse のコールバックは、コントローラがレスポンスを返したあと、カーネルが send() するときに実行されます。Symfony 6では kernel.response イベントの段階でセッションが保存されて閉じられるため、コールバック内の $this->session->get(...) はセッションを開き直そうとし、しかしヘッダは既に送出済みなので例外になります。
EC-CUBEのCSV出力では、検索条件をセッションから読む処理がコールバック内にあります。4.0(Symfony 3.4)ではコールバックの時点でもセッションがまだ開いていたので動いていました。StreamedResponse の setCallback 周辺で、コールバックの中からセッションを読んでいる箇所を探すと、該当箇所が見つかります。
コントローラ内でsend()してから返す
公式の4.2以降の OrderController::exportCsv() と同じ形にします。$response->send() をコントローラ内で呼ぶと、その時点でコールバックが実行されるので、セッションはまだ開いています。
$response = new StreamedResponse();
$response->setCallback(function () use ($request, $csvTypeId) {
// ここでセッションから検索条件を読み、CSVを書き出す
});
$response->headers->set('Content-Type', 'application/octet-stream');
$response->headers->set('Content-Disposition', 'attachment; filename='.$filename);
// Symfony 6 ではセッションが kernel.response で先に閉じられるため、
// コントローラ内で送出してコールバックをここで実行させる
$response->send();
return $response;
戻り値の $response はカーネルが再度 send() しますが、StreamedResponse は2回目の送出を無視するので二重出力にはなりません。4.0からの移植では、受注CSV・会員CSV・独自のCSV連携など StreamedResponse を返しているアクションを全部見て、公式コードと同じく send() が入っているかを確認します。ヘッダ行を書き出す exportHeader() の呼び出しも、パッチ適用時に落ちていることがあるので同時に見ておくと安全です。
EC-CUBEに関するお問い合わせ
[重要]現在公式にセキュリティサポートが切れていないPHPは8.1以上、MySQLは8.0以上で、対応しているEC-CUBEバージョンは4.2以上です。古いEC-CUBEを使っている方は適切なタイミングでバージョンアップをご検討ください。