2016年ニジボックス Advent Calendar 4日目の記事です。
回想
このようなシーンはたまにはあるでしょう。特に新人研修の時。
後輩:このPHPファイルの最後でPHPタグ閉じ(?>)はないっすね。大丈夫ですか?
先輩:フフ、PHPコードの次でHTMLなどはないから、閉じなくても大丈夫だよ。むしろない方がいいよ。
後輩:へぇぇ、なんでですか。
先輩:この状況で閉じを書かなくても自動で補完してくれるの。逆に閉じタグを書いちゃったら、もしその後改行でも入れたら、他のPHPファイルから「require」される時、呼び出したところで改行を出力してしまう。万が一その次で「header」を出力しようとしたら効かなくなる。知ってると思うけど、headerの前にコンテンツ出力あっちゃいけないのよ。あったらもheaderを変更できなくなっちゃう。
後輩:ほーほー、勉強になりますね!
先輩:こんぐらいは常識さ! d( ̄ ・ ̄)
本題
今時のPHP開発は大抵何かのフレームワークを使って行います。リダイレクトするのも一々header関数を呼び出したりをせず、フレームワークから提供してくれた使いやすい関数でやります。ただし、いざ素のPHPで何か小さい機能を作る時は、やはり上の会話で出た常識をちゃんと意識して、header出力を処理の一番先頭で書くようにしますね。
果たして、header出力は本当に一番先頭で書かなきゃいけないのか、今回これを検証していきたい、と思いまーす!
検証
<?php
header('Location: https://google.co.jp');
exit;
いかにもシンプルなheader出力、予想どおりで実行したらGoogleさんへリダイレクトしました。
問題はこれからです!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
TEST
</body>
</html>
<?php
header('Location: https://google.co.jp');
exit;
ファイルの先頭でHTMLのタグが先に書かれました。
いざ実行したら、これもGoogleさん行きになりました。headerの前でHTML書かれても大丈夫のようですね。
さって次。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
TEST
</body>
</html>
<?php
echo 'TEST';
header('Location: https://google.co.jp');
exit;
あれれ、PHPの出力もheaderの前でやったのに、これもちゃんとリダイレクトしましたね。headerの前で出力はありなの?どういうことっすか先輩?
原因
header関数の前のクライアントまでコンテンツを出力したら、もちろんHTTPボディーの前にHTTPヘッダーが先行し、それ以降再度header関数でリダイレクトしようとしても実行できません。ただし処理実行中のコンテンツ出力すぐクライアントまで行くわけではなく、まずバッファーで溜め込んで、特別な処理をやらない限り、コンテンツはバッファーの設定上限までまたまたheader出力のチャンスがあります。
そのバッファーの上限はphp.iniでデフォルトの値が設定されています。
output_buffering = 4096
4Kですね、検証してみましょう。
<?php
for ($i = 0 ; $i < 1024; $i++) {
echo 'TEST';
}
header('Location: https://google.co.jp');
exit;
結果、4Kの「TEST」がheaderの前に出力されて、バッファーの上限を超え、リダイレクトがされなくなりました。
まとめ
前述の「output_buffering」がPHP4.1までは「Off」で設定されています。もちろんそれでスペース一個の出力で、それ以降のheader関数をだめにしちゃいます。それで上の会話のような言い伝えが生まれた原因かもしれません。
またheader関数の前にflush関数を呼び出してバッファーを強制出力させたり、「output_buffering」をOffにされたりする可能があるため、やはりheaderをできるだけ先頭に置いとくほうが無難ですね。