5454
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](https://stackoverflow.com/q/8028957/4369063)
> [@Moses89](https://stackoverflow.com/users/1031807)
CC BY-SA 3.0
2015-01-27T16:34:55.653
5453
2015-01-28T01:43:07.967
2017-05-23T12:38:56.083
-1
2944
question
php
PHP の `headers already sent` エラーはどう直したらいいですか？
# エラーの理由

このエラーは **HTTP ヘッダーを変更する関数** （後述）の呼び出しより前に、すでに **何か** が **アウトプット**
されているために発生します。

### HTTP ヘッダーを変更する関数

HTTP ヘッダーを変更する関数には以下のようなものがあります。

  * **HTTP ヘッダーを変更するもの**

    * [`header`](http://php.net/header)
    * [`header_remove`](http://php.net/header_remove)
  * **クッキーに関するもの**  
（クッキーの操作には `Set-Cokkie:` HTTP ヘッダーを用います）

    * [`setcookie`](http://php.net/setcookie)
    * [`setrawcookie`](http://php.net/setrawcookie)
  * **セッションに関するもの**  
（クッキー制御やキャッシュ制御などのために `Set-Cokkie:`、`Expires:`、`Cache-Control:` HTTP
ヘッダーなどを出力します）

    * [`session_start`](http://php.net/session_start)
    * [`session_regenerate_id`](http://php.net/session_regenerate_id)

### アウトプット

アウトプットには以下のようなものがあります。

  * **意図していないアウトプット**

    * `<?php` の前や、`?>` の後にある、スペースや改行の出力

    * PHP ファイルの先頭にある UTF-8 の [BOM](http://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%88%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%E3%83%9E%E3%83%BC%E3%82%AF) の出力

    * PHP による Error や Warning メッセージ出力

  * **意図したアウトプット**

    * `print`、`echo`、`readfile`、`passthru` など出力を伴う関数での出力
    * `<?php` の前や、 `<?php` 〜 `?>` の外にある、生の `<html>` コードなどの出力

## どういうことか？

なぜアウトプットの前にヘッダーを送信する必要があるのか、代表的な
[HTTP](http://ja.wikipedia.org/wiki/Hypertext_Transfer_Protocol) レスポンスを見てみます。  
HTTP レスポンスは以下のようなものです。

```

    HTTP/1.1 200 OK
    Powered-By: PHP/5.3.7
    Vary: Accept-Encoding
    Content-Type: text/html; charset=utf-8
    
    <html><head><title>PHP page output page</title></head>
    <body><h1>Content</h1> <p>Some more output follows...</p>
    and <a href="/"> <img src=internal-icon-delayed> </a>
    
```

HTTP レスポンスは、 **ヘッダー** 部と実データの **ボディ** 部に分かれ、それらが空行で区切られ送信されます。常に実データ（アウトプット）の
**前に** ヘッダーは送信されます。

PHP スクリプトは主に HTML コンテンツを生成しアウトプットしますが、HTTP ヘッダーも同時に送信します。

PHP はアウトプット（`echo` や `print`、`<html>` など）が行われると、全ての 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としてパースされない、`<?php` より前や、 `<?php` 〜 `?>` の外にあるブロックは、直接アウトプットされます。  
**HTTP ヘッダーを変更する関数** より前にこのようなブロックは存在できません。

```     <!DOCTYPE html>

    <?php
        // ヘッダーはすでに送信されています。
        // もう HTTP ヘッダーを変更する関数 は利用出来ません。
    
```

制御ロジックとアウトプットロジックを分離するようにします。

    * スクリプトの先頭に制御ロジックを記述します
    * 一時的な文字列変数などを用いて後でアウトプットできるようにします
    * 実際のアウトプットロジックと HTML の出力は最後に行います

制御ロジックとアウトプットロジックの分離のために、[Smarty](http://www.smarty.net/docs/ja/) や
[Twig](http://twig.sensiolabs.org/) といったテンプレートエンジンを使うのもいいと思います。

  3. ### `<?php` の前にあるスペースによる "script.php **line 1** " Warning

1 行目で出力がある、という Warning が表示されている場合、PHP 開始タグ `<?php` の前にスペースや空行がある場合が多いです。

```      <?php

    // ファイルの先頭には <? の前に隠されたスペースが存在します。
    
```

同様に、スクリプトのブロックの間にスペースや空行が存在することもあります。

```     ?>

    
    <?php
    
```

実際には、PHP は閉じタグ `?>` の後にある **一つの** 改行は無視します。  
ですが、複数行の改行やタブやスペースが含まれているとそれがアウトプットされます。

  4. ### UTF-8 BOM

上記した改行やスペース以外にも同じような問題が発生する **見えない** 文字もあります。

よく知られているのは UTF-8
[BOM](http://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%88%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%E3%83%9E%E3%83%BC%E3%82%AF)
です。これは多くのテキストエディタで見えませんが、ファイルの先頭に `EF BB BF` というバイト列が含まれます。PHP
はこのバイト列をそのままアウトプットしようとします。

これを解決するには、ファイル保存時に「UTF-8（BOMなし）」や「UTF-8N」などの BOM が出力されない指定を行ってください。  
Windows のメモ帳は BOM なしで保存することが出来ない（未確認）ため、他のエディタの利用をお勧めします。

  5. ### `?>` の後ろにあるスペース

PHP 終了タグ `?>` の後でアウトプットがあると表示される場合、終了タグの後でスペースや生のテキストが出力されています。PHP
終了タグ以降もスクリプトの実行は行われるため、PHP 終了タグの後のスペースやテキストはアウトプットされます。

なお、PHP 終了タグ `?>` は必須ではないため、必要のない場合は `?>`
を省略することをお勧めします。これにより、この問題の発生を低減できます。（このケースの多くは、`include` や `require`
したファイルで起きます）

```     <?php

    // ...いろいろな記述
    // 最後の ?> は不要
    
```

  6. ### "Unknown on line 0" とエラー表示される場合

これは、PHPエクステンションや php.ini 設定に問題がある場合が多いです。

    * PHP モジュールの二重ロードや、php.ini 設定の不備により、PHP の開始時の Warning メッセージが出力されている場合。
    * `gzip` ストリームエンコーディング設定、[`ob_gzhandler`](https://stackoverflow.com/a/622419/4369063) で起こる場合があります。
  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
の[アウトプットバッファリング（出力制御）](http://php.net/manual/ja/intro.outcontrol.php)を行う事でこの問題を回避することもできます。

  1. [`output_buffering=`](http://php.net/manual/ja/outcontrol.configuration.php)設定を [php.ini](http://www.php.net/manual/ja/configuration.file.php) か、[.htaccess](http://www.php.net/manual/ja/configuration.changes.php) などで行います。  
これを有効にすると、PHP はアウトプットをすぐに Web サーバーに送信せず、バッファリングします。それにより、PHP が HTTP
ヘッダを集約できます。

  2. 起動スクリプトの最初に [`ob_start();`](http://php.net/ob_start) を呼び出して同様のことを行えます。  
ただし、スクリプトの最初で `<?php ob_start(); ?>` をしたとしても、それより前に BOM
や空白が含まれていた場合、回避することはできません。

ただし、アプリケーションが生成した画像などバイナリデータを送信しようとする場合、バッファリングされた余分な空白が問題となったり、開発環境の構築や、開発サーバーと運用サーバーとの切り替えで問題が生じたりする場合があるため、推奨はできません。

[基本的な使用法](http://www.php.net/manual/ja/outcontrol.examples.basic.php)も併せて参照ください。

## `headers_sent()` によるチェック

[`headers_sent()`](http://php.net/headers_sent) により、すでにヘッダーが送信済みかどうかをチェックできます。  
これは条件によって情報を出力したり、フォールバックを行う際に便利です。

```

    if (headers_sent()) {
        die("リダイレクトに失敗しました。このリンクをクリックしてください: <a href=...>");
    } else{
        // リダイレクトする
        exit(header("Location: /user.php"));
    }
    
```

### 他の回避方法

  * ### HTML `<meta>` タグでのリダイレクト

もし、アプリケーションの修正が構造的に難しいような場合、HTML の `<META>` タグを用いてリダイレクトを行うことも可能です。

```     <meta http-equiv="Refresh" content="0; URL=/user.php">

    
```

  * ### JavaScript でのリダイレクト

代替手段として [JavaScriptでのリダイレクト](https://stackoverflow.com/questions/503093/how-
can-i-make-a-redirect-page-in-jquery-javascript)を行う事もできます。

```      <script> location.replace("target.html"); </script>

    
```

ただし、JavaScript が動くブラウザでのみしか利用出来ません。

## 関連リンク

  * Stack Overflow - [How to fix “Headers already sent” error in PHP](https://stackoverflow.com/q/8028957/4369063)
  * The Wordpress FAQ [Headers already sent エラーを解決するには？](http://wpdocs.sourceforge.jp/FAQ/%E3%83%88%E3%83%A9%E3%83%96%E3%83%AB%E3%82%B7%E3%83%A5%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0#Headers_already_sent_.E3.82.A8.E3.83.A9.E3.83.BC.E3.82.92.E8.A7.A3.E6.B1.BA.E3.81.99.E3.82.8B.E3.81.AB.E3.81.AF.EF.BC.9F)

> この回答は以下の本家SOの回答を翻訳・再構成したものです。  
>  <https://stackoverflow.com/a/8028987/4369063>
> [@mario](https://stackoverflow.com/users/345031/mario)  
>  <https://stackoverflow.com/a/8028979/345031>
> [@phihag](https://stackoverflow.com/users/35070/phihag)
CC BY-SA 3.0
2015-01-27T16:34:55.653
5454
2015-01-28T01:43:07.967
2017-05-23T12:38:55.250
-1
2944
5453
answer