#5985

見えない文字列


web制作

今日はabemaTVで無料映画『遊☆戯☆王 THE DARK SIDE OF DIMENSIONS』を見た後、
たまたま気が向いたので特設サイト「ピクチャレ大会」で長らく残っている不具合を解消するべく、
PHPコードと格闘していました。結論から言えば、倒すことができました。

不具合は実にシンプルで、「COOKIEが正常保存されない」というもの。
クッキーとは、前にも説明しましたが、
そのサイトを訪問・利用すると発行されるパスポートのようなものです。
例えば初回訪問時にユーザー名を登録しておくと、
2回目に訪問したときにあらかじめユーザー名が入った状態で表示することができるようになります。
ピクチャレ大会はこの仕組みを使って記録投稿時の手間を軽減しています。

ところが、あるときから突然、この機能が使えなくなりました。
しかも、ローカル環境では難なく動いているのに、本番環境になると突然動かなくなる。
PHPのバージョンが違うわけでもないし、setcookie関数の仕様が変わったわけでもない。
.iniの項目を端から端まで読み込んでも、この問題に関連しそうな項目はない……。
というわけで随分前に匙を投げていました。

今回は、まずどこでプログラムが止まっているのかを突き止めることに。
値をクッキーとして発行するための関数「setcookie」は、
成功したか失敗したかを戻り値として返す仕組みがあります。
まずはこれで関数が正常に動いているかを確認。するとやっぱり正常に動いていない。
本番環境でエラー表示してみると、「Cannot modify header information: headers already sent」
というエラーが。ローカルのときはこんなの見たことありませんでした。不具合の正体はこれか!!
調べてみると、このエラー文はヘッダーを二重に読み込もうとすると発生するらしい。

説明が大変難しいのですが、普通webサイトには「ヘッダー」というものが存在します。
目に見えるページと違い、ヘッダーは目に見えません。
ヘッダーは、このページがどういうページなのかといった定義付けをしたり、
前のページから値を受け取ったり、通信状況を交換したりといった役目があります。
ヘッダーは2つ存在できません。絶対に1ページにつき1つまでです。

PHPのheader関数やsetcookie関数はヘッダーの処理に該当するため、
必ずページの先頭に書かなければならないという決まりがあります。
より正確に言えば、何らかの出力をする前の部分におかなければなりません。
何故かというと、何らかの出力をするところでプログラムは勝手にヘッダーを出力するからです。
PHPは「echo ‘hello world’;」と書いただけでもきちんと動作しますが、
それは裏で自動的にヘッダーを付与しているからです。

なので、「echo ‘hello world’;」よりも後でヘッダーの処理であるsetcookieなどを書いてしまうと、
頭と胴体ができたのにそこにさらに頭をくっつけることになり、奇妙な形になってしまいます。
プログラムは、それを避けるために2つ目以降の頭が出てきたら弾くようになっています。
今回、setcookieが弾かれたのはそのせいです。

しかし、プログラムを読んでいくと、どう考えてもsetcookie関数より前に出力はしていない。
試しに一番最初にsetcookie関数をおいても、それでもエラーになってしまいます。
やっぱりサーバー側に問題があるのではないかと思い、
別ファイルを作って簡単なテストページを作ってみたら、なぜかそっちではうまく動作する。
ということはやっぱりファイルに原因があるということです。

散々調べて行き着いた原因は、文字コードでした。
自分もよく分かっていないのですが、
UTF-8にはオプションとして、バイトオーダーマーク(Byte Order Mark、通称BOM)
の有無を選択することができるようになっています。
BOMは、簡単に言えば文字コードを判別するために先頭にほんの少し文字列を追加するというもの。
これによって、今から出力する文字コードがUTF-8ということが判別できるわけですね。

ところが、PHPはこの「ほんの少しの文字列」がプログラム外の出力文字列と認識したのでしょう。
結果として、BOMの前にヘッダーが出力されたことで頭が1つできてしまい、
その後は例え1行目だろうがヘッダーを設定することができなくなっていたというわけです。

PHPでプログラムを作成するときはBOMなしで保存しましょう。
プログラマーの間では常識なのかもしれませんが、最近エディターを変えたということもあり、
完全に失念していました。そしてまさか、こんな深刻な問題を引き起こすなんて……。

同じファイルでもローカル環境では無事に動いていたのは未だに謎ですが、
とにかく解決できて良かったです。
いつものことですが、こういう厄介な問題って本当にシンプルなところに答えがあるんですよね。
以前の研修でも同じようなことがありましたが、
解決してみれば「たったそれだけのことだったのか!」とげんなりすることが多いです。
まぁ、大幅な改修作業が必要な不具合じゃなくて良かったです本当に。

今日はうっかり14時半に起きてしまいました。
日中は上記の通りプログラムと格闘していたので非充実感はありませんでしたが、
この起床時間は大いに問題があります。ということで今日は土曜日ですがお酒を呑んで寝るつもり。
昨夜はお酒は呑まなかったのですが、呑まないとそれはそれで寝過ぎるようなんですよね……。
この融通の利かない身体にも困ったもんだ。

0

コメントを残す