PHPのheader()で「headers already sent」が出る理由と直し方
「headers already sent」エラーとは、HTTPヘッダーが送信済みの状態で header() 関数を呼び出したときにPHPが発する警告です。
ログイン処理のリダイレクトや、Cookieを設定するためのheader()が、ある日突然「Warning: Cannot modify header information」で止まる。原因を探すと大抵、header()の中身とは無関係な、離れた場所の1文字だったりします。パターンはだいたい決まっているので、知っておくと調査が早くなります。
なぜこのエラーが起きるのか
PHPはリクエストへの応答として、まずHTTPヘッダー(ステータス行やLocation、Set-Cookieなど)を送り、そのあとにHTML本体などのボディを送ります。ヘッダーはボディより先に送らなければならない、というHTTPの構造上の制約があるためです。
問題は、echoやprintは呼んでいなくても、PHPファイルの中に「PHPタグの外側にあるただの文字」が1つでもあると、それだけでボディの送信が始まってしまう点です。ボディの送信が始まった時点でヘッダーは確定してしまうので、その後にheader()を呼んでも変更できず、警告が出ます。
見えない1文字が犯人になることが多い
典型的なのは、共通処理を書いた設定ファイルなどを include した際に、そのファイルの終わりの閉じタグ ?> の後に空行や空白が残っているケースです。
// config.php (末尾に注目)
<?php
define('APP_ENV', 'production');
?>
// ↑ この閉じタグの後の空行がそのまま出力される
この空行はPHPコードではなく「ただのテキスト」として扱われるため、そのままレスポンスボディとして出力されます。config.php を include した時点で、その1行の空白がすでに送信済みになり、後続の処理でheader()を呼んでも間に合いません。同様に、UTF-8のBOM(ファイル先頭に付く見えない数バイト)が付いたファイルも、PHPタグの前に「出力」があるのと同じ扱いになります。
対策として実務でよく使われるのは、ライブラリ的な役割のファイル(クラス定義や設定ファイルなど、直接HTMLを出力しないファイル)では閉じタグ ?> をそもそも書かない、というルールです。ファイルの終わりまでPHPコードなら、閉じタグを省略しても文法上問題はなく、末尾の余分な空白がボディに漏れる事故を防げます。
エラーメッセージはどこで送信されたかを教えてくれる
「headers already sent by (output started at /path/to/config.php:4)」のように、警告メッセージには最初に出力が始まったファイル名と行番号が含まれます。header()を呼んでいる行ではなく、この「output started at」の場所を見に行くのが正しい調査手順です。
headers_sent()で事前に気づく
すでにヘッダーが送信済みかどうかは、header()を呼ぶ前に headers_sent() で確認できます。これを使うと、警告を出す前に別の処理へ切り替える、といった防御的な書き方ができます。
if (!headers_sent($file, $line)) {
header('Location: /login');
exit;
}
// すでに送信済みだった場合の代替処理
echo '<script>location.href="/login";</script>';
exit;
headers_sent()は、送信済みなら true を返すだけでなく、第1引数・第2引数を参照渡しで受け取ることで、出力が始まったファイル名と行番号を取得できます。これをログに出しておくと、エラー画面に頼らずに原因箇所を特定できます。
出力バッファリングは対症療法だと割り切る
ob_start() で出力バッファリングを開始しておくと、本体の出力はすぐには送信されずバッファに溜まるため、その後でheader()を呼んでも「送信済み」の状態にならず、エラーを回避できます。手っ取り早い対処として使われがちですが、これは「余計な出力が発生している」という根本原因を隠しているだけです。バッファの上限を超えれば結局出力は流れてしまいますし、原因のファイルを直さない限り、別の場所で同じ問題が再発します。応急処置と割り切って、落ち着いたタイミングで出力元を潰しておくのがおすすめです。
まとめ
「headers already sent」は、header()の書き方そのものより、それより前にどこかで意図しない出力が発生していることが原因になっているケースがほとんどです。設定ファイルやクラスファイルの閉じタグの後の空白、BOM付きの保存、includeしたファイルの中のちょっとしたechoなど、犯人は大抵地味な場所にいます。エラーメッセージの「output started at」を手がかりに探し、疑わしいファイルでは閉じタグを省略するくらいの予防線を張っておくと、この手のエラーに振り回される時間はかなり減らせると思います。
よくある質問
Q. header()を呼ぶ前にechoでデバッグ出力してもエラーになりますか?
A. なります。var_dumpやechoも「出力」として扱われるため、header()より前に置くとその時点でヘッダーが確定してしまいます。デバッグ出力はheader()を呼び終えた後に置くか、error_logなど画面に出さない方法に切り替えてください。
Q. ob_start()を使えばもうこのエラーは気にしなくていいですか?
A. 出力バッファリングでその場のエラーは止まりますが、原因になっている余計な出力そのものは残ります。バッファサイズの上限や、別のページでのバッファ未使用時に同じ問題が再発する可能性があるため、根本原因のファイルを直すことをおすすめします。
Q. 閉じタグ ?> を省略しても大丈夫なのでしょうか?
A. 問題ありません。ファイルの終わりまでPHPコードだけの場合、閉じタグの省略は文法上正しい書き方で、むしろ末尾の空白によるエラーを防ぐ目的で推奨されることが多いです。