質問: PHP スクリプトを実行したところ、以下の様なエラーが表示されました。 > > Warning: Cannot modify header information - headers already sent by > > (**output started at /some/file.php:12**) in **/some/file.php** on **line 23** > > > エラー行では `header()` と `setcookie()` を行っています。 エラーの理由と、どうやって解決したらいいのかを教えてください。 > > この質問は以下の翻訳です。 > > Stack Overflow - How to fix “Headers already sent” error in PHP @Moses89 > > >
回答: エラーの理由 ====== このエラーは **HTTP ヘッダーを変更する関数**(後述)の呼び出しより前に、すでに**何か**が**アウトプット**されているために発生します。 ### HTTP ヘッダーを変更する関数 HTTP ヘッダーを変更する関数には以下のようなものがあります。 * **HTTP ヘッダーを変更するもの** + `header` + `header_remove` * **クッキーに関するもの** (クッキーの操作には `Set-Cokkie:` HTTP ヘッダーを用います) + `setcookie` + `setrawcookie` * **セッションに関するもの** (クッキー制御やキャッシュ制御などのために `Set-Cokkie:`、`Expires:`、`Cache-Control:` HTTP ヘッダーなどを出力します) + `session_start` + `session_regenerate_id` ### アウトプット アウトプットには以下のようなものがあります。 * **意図していないアウトプット** + `` の後にある、スペースや改行の出力 + PHP ファイルの先頭にある UTF-8 の BOM の出力 + PHP による Error や Warning メッセージ出力 * **意図したアウトプット** + `print`、`echo`、`readfile`、`passthru` など出力を伴う関数での出力 + `` の外にある、生の `` コードなどの出力 どういうことか? なぜアウトプットの前にヘッダーを送信する必要があるのか、代表的な HTTP レスポンスを見てみます。 HTTP レスポンスは以下のようなものです。 ``` HTTP/1.1 200 OK Powered-By: PHP/5.3.7 Vary: Accept-Encoding Content-Type: text/html; charset=utf-8 PHP page output page Content Some more output follows... and ``` HTTP レスポンスは、**ヘッダー**部と実データの**ボディ**部に分かれ、それらが空行で区切られ送信されます。常に実データ(アウトプット)の**前に**ヘッダーは送信されます。 PHP スクリプトは主に HTML コンテンツを生成しアウトプットしますが、HTTP ヘッダーも同時に送信します。 PHP はアウトプット(`echo` や `print`、`` など)が行われると、全ての HTTP ヘッダーを送出し、ボディ部の出力を開始します。 また、ボディ部の出力が開始された後には、ヘッダーを送信する(し直す)ことは出来ません。 アウトプットが行われた後に **HTTP ヘッダーを変更する関数**が呼ばれると、ヘッダーを変更する事が出来ないため以下のエラーが発生します。 > > Warning: Cannot modify header information - headers already sent (output started at *script:line*) > > > 問題の解決方法 ======= `header()` warning メッセージには、問題に関連した情報が含まれています。 > > Warning: Cannot modify header information - headers already sent by > > (**output started at /some/file.php:12**) in **/some/file.php** on **line 23** > > > 上記のエラーは、`/some/file.php` の 23 行目で `header()` 関数の呼び出しに失敗したということです。 また "**output started at**" のカッコ書き部分に、アウトプットがすでに行われた箇所が書かれています。この場合、`/some/file.php` の **12行目** がそれにあたります。 これらを元に、出力の前に `header()` 関数を呼び出すか、`header()` 関数が呼び出された後でアウトプットを開始するようにすれば問題が解決できます。 よくあるケース 1. ### print, echo `print` や、`echo` 文など意図的なアウトプットを伴う文や関数より後に **HTTP ヘッダーを変更する関数**を使用することは出来ません。 アウトプットの前に **HTTP ヘッダーを変更する関数**を呼び出すように変更してください。 以下のような関数はアウトプットを伴います。 * `print`, `echo`, `printf`, `vprintf` * `trigger_error`, `ob_flush`, `var_dump`, `print_r` * `readfile`, `passthru`, `imagepng`, `imagejpeg` 2. ### 生の HTML ブロック PHPとしてパースされない、`` の外にあるブロックは、直接アウトプットされます。 **HTTP ヘッダーを変更する関数** より前にこのようなブロックは存在できません。 ``` ` の後にある **一つの** 改行は無視します。 ですが、複数行の改行やタブやスペースが含まれているとそれがアウトプットされます。 4. ### UTF-8 BOM 上記した改行やスペース以外にも同じような問題が発生する**見えない**文字もあります。 よく知られているのは UTF-8 BOM です。これは多くのテキストエディタで見えませんが、ファイルの先頭に `EF BB BF` というバイト列が含まれます。PHP はこのバイト列をそのままアウトプットしようとします。 これを解決するには、ファイル保存時に「UTF-8(BOMなし)」や「UTF-8N」などの BOM が出力されない指定を行ってください。 Windows のメモ帳は BOM なしで保存することが出来ない(未確認)ため、他のエディタの利用をお勧めします。 5. ### `?>` の後ろにあるスペース PHP 終了タグ `?>` の後でアウトプットがあると表示される場合、終了タグの後でスペースや生のテキストが出力されています。PHP 終了タグ以降もスクリプトの実行は行われるため、PHP 終了タグの後のスペースやテキストはアウトプットされます。 なお、PHP 終了タグ `?>` は必須ではないため、必要のない場合は `?>` を省略することをお勧めします。これにより、この問題の発生を低減できます。(このケースの多くは、`include` や `require` したファイルで起きます) ``` は不要 ``` 6. ### "Unknown on line 0" とエラー表示される場合 これは、PHPエクステンションや php.ini 設定に問題がある場合が多いです。 * PHP モジュールの二重ロードや、php.ini 設定の不備により、PHP の開始時の Warning メッセージが出力されている場合。 * `gzip` ストリームエンコーディング設定、`ob_gzhandler` で起こる場合があります。 7. ### エラーメッセージ **HTTP ヘッダーを変更する関数**より前の文で PHP の Warning や Notice エラーがある場合、それがすでにアウトプットされているため、このエラーが起きます。 エラーメッセージが表示されない場合 `php.ini` で `error_reporting` や `display_errors` をオフにしている場合、エラーが表示されていなくても問題がある場合があります。 例えば、`header("Location: ...")` でリダイレクトを行おうとしても、このエラーが起きている場合、リダイレクトは失敗します。 このような場合、スクリプトの最上部で以下のようにしてエラーを表示させるようにする事ができます。 ``` error_reporting(E_ALL); ini_set("display_errors", 1); ``` または `set_error_handler("var_dump");` のようにエラーが起きた場合のハンドラを指定する事もできます。 アウトプットバッファリング(出力制御)による回避 PHP のアウトプットバッファリング(出力制御)を行う事でこの問題を回避することもできます。 1. `output_buffering=`設定を php.ini か、.htaccess などで行います。 これを有効にすると、PHP はアウトプットをすぐに Web サーバーに送信せず、バッファリングします。それにより、PHP が HTTP ヘッダを集約できます。 2. 起動スクリプトの最初に `ob_start();` を呼び出して同様のことを行えます。 ただし、スクリプトの最初で `` をしたとしても、それより前に BOM や空白が含まれていた場合、回避することはできません。 ただし、アプリケーションが生成した画像などバイナリデータを送信しようとする場合、バッファリングされた余分な空白が問題となったり、開発環境の構築や、開発サーバーと運用サーバーとの切り替えで問題が生じたりする場合があるため、推奨はできません。 基本的な使用法も併せて参照ください。 `headers_sent()` によるチェック `headers_sent()` により、すでにヘッダーが送信済みかどうかをチェックできます。 これは条件によって情報を出力したり、フォールバックを行う際に便利です。 ``` if (headers_sent()) { die("リダイレクトに失敗しました。このリンクをクリックしてください: "); } else{ // リダイレクトする exit(header("Location: /user.php")); } ``` ### 他の回避方法 * ### HTML `` タグでのリダイレクト もし、アプリケーションの修正が構造的に難しいような場合、HTML の `` タグを用いてリダイレクトを行うことも可能です。 ``` ``` * ### JavaScript でのリダイレクト 代替手段として JavaScriptでのリダイレクトを行う事もできます。 ``` ``` ただし、JavaScript が動くブラウザでのみしか利用出来ません。 関連リンク * Stack Overflow - How to fix “Headers already sent” error in PHP * The Wordpress FAQ Headers already sent エラーを解決するには? > > この回答は以下の本家SOの回答を翻訳・再構成したものです。 > > @mario > > @phihag > > >