読者です 読者をやめる 読者になる 読者になる

PHPでHTTPレスポンスコードを返すときPHP_SAPI分岐は要るのか

PHP

こたえは「たぶん要らないんじゃないの」です。はいおわり、というのでは何の話かわからないので、以下に詳しく書いておきます。

PHPのマニュアルで header() 関数を調べるとこんなことが書いてありました。

特殊な header コールが 2 種類あります。最初のものは、 文字列 "HTTP/" から始まる全てのヘッダ (大文字・小文字は区別されません) です。 このヘッダは、送信する HTTP ステータスコードを示すために使用されます。 例えば、存在しないファイルへのリクエストを処理するためにある PHP スクリプトを使用するよう (ErrorDocument ディレクティブにより) Apache を設定する場合、 そのスクリプトが正しいステータスコードを返すようにする必要があります。

< ?php
header("HTTP/1.0 404 Not Found");

FastCGI の場合は、404 のレスポンスは次の形式でなければなりません。

< ?php
header("Status: 404 Not Found");

http://www.php.net/manual/ja/function.header.php

Statusの方式で返すのは、FastCGIというよりCGI一般の仕様なので、旧来からのCGIにも通じる話です。で、これに従うと、ステータスコードを返す正しい処理というのは以下のようなかたちが正しいということになります。

if(preg_match('/^cgi/', PHP_SAPI)) {
    // Status: のほうを使う
}
else {
    // HTTP/ のほうを使う
}

ところが、これ指定しなくてもなんか動いてるみたいでした。で、気になって調べると、CakePHP2とSymfony2のソースにはこの分岐がありませんでした。逆に、Smartyには php_sapi_name() で分岐が書かれていました。テンプレートエンジンになんでそんなものが、というのは、304 Not Modified 対応っぽいです。そんなものはコントローラでやるわいというのはさておき...

nginxとFPMで実績がありそうなほうがCGIを意識してないのはおかしいぞというわけで、じっさいにCGIでPHPを叩くとどうなるか調べました。これがPHPの(Fast)CGIにおける真実です。

はい、結論。CGIモードのPHPは HTTP/ で指定したヘッダがあったらそれをStatusに置き換えてCGIの応答に使い、それを受けたApacheやnginxが勝手にレスポンスコードの記述に変換してくれる。プロトコルバージョンで1.0を指定しているのに1.1になってしまうのがその証拠。
まあ、試した環境が新しすぎてそういう機能があるのかなとも思うので、PHPの5.0代とか持ってる人がいたら試してもらえると嬉しいです。

そうそう、ApacheでCGIにPHPを使いたいときは、php.iniの cgi.force_redirect = 0 を忘れるとハマるのでご注意ください。