先日、とある案件で、PHP4で動いているサイトを移行する、という案件がありました。
普通なら、PHP7に対応させる方向に提案するところですが、先方も予算がないらしく移行先もPHP4が動く環境を用意するとのこと。
そんなわけで、仕方なく破格の値段で受注しました(泣)
通常の(特に特殊な要件がない)PHP+Apache+MySQLサイトの移行であれば、2万円くらいで受注できればラッキーな感じなのですが、今回はPHP4だったり、他にもややこしい要件があったので2.5万円で受注したのですが、予想通り大赤字を垂れ流す結果になりました、、、
くやしいので大ハマりしたところをブログのネタにしてやろうと思います。
原因不明の文字化けに遭遇したらmbstring.internal_encodingの設定を確認してみよう
というわけで、先に結論を書きました。
PHP+Apache+MySQLの環境で文字化けの事象が起こると、トラブルシュートに時間がかかることは良くあります。
文字化けの原因は色んな所にあるのですが、今回は意外にもphpの設定が原因でした。
その設定というのがmbstring.encoding_translationというものなのですが、これがONになっていると、受け付けたリクエスト中の文字列を自動的にPHPの内部文字エンコーディング(※1)に変換してしまうという恐ろしい設定のようです。
ほとんどの環境ではこの設定はOFFになっていると思うのですが、もし原因不明の文字化けに悩まされている場合はこの設定を確認してみると良いかもしれません。
※1PHPの内部文字エンコーディングについては別の記事で解説できれば、、、
確認方法
最も簡単な確認方法は、phpでphpinfo()を使ってphp.iniの設定内容を表示する方法です。
例えば以下のようなファイルを作って、「phpinfo.php」などと適当に名前を付けてブラウザでアクセスすれば良いですね。
<?php
phpinfo();
phpの設定を表示したら、「mbstring」などと検索してみます。
すると、下のような設定項目が出てきます。
で、このうちmbstring.encoding_translationがONになっていると、予期せぬ動作をしている可能性があります(デフォルトはOFF)。
mbstring.encoding_translationがONになると何が起こる?
今回の移行案件では、移行元でこの設定がONになっていたのに対し、移行先ではもちろんデフォルト値のOFFになっていたのが原因でした。
それではmbstring.encoding_translationがONになっていると何が起こるのかを簡単に解説しておきます。
上で「mbstring.encoding_translationがONになっていると、受け付けたリクエスト中の文字列を自動的にPHPの内部文字エンコーディングに変換してしまう」と書きましたが、これでは分からない人も多いんじゃないかと思います。
HTTPリクエストとは?
Webページがブラウザに表示される際、必ずブラウザからのリクエストが最初に発生します。言い換えるとブラウザからのHTTPリクエストがサーバに飛んで、サーバがそのリクエストをPHPで処理する、という流れですね(ブラウザ⇒PHPまでの途中のプロセスをかなり省略して表現しています。)。
最も簡単なパターンだと普通にyahoo.co.jpにアクセスする場合はこんな感じになっているんじゃないかと思います(実際はブラウザによってもっと多くのパラメータが設定されていると思いますが、ここでは簡単のため)。
GET / HTTP/1.1
Host: yahoo.co.jp
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: */*
これだけ見ると、日本語は入っていないので文字化けなど起こる要素はないのですが、実はHTTPリクエストにはGETやPOSTで日本語を含めることができます。
GETリクエストは、よく見るURLの最後に?で続けるやつですね。
良い例が浮かばないのですが、例えばAmazonで「ビーフジャーキー」と検索すると、表示されるページのURLは以下のようになっています。
このk=ビーフジャーキーの部分が日本語ですね。
この時、HTTPリクエストはだいたい以下のようになっているんじゃないかと思います。
GET /s?k=%E3%83%93%E3%83%BC%E3%83%95%E3%82%B8%E3%83%A3%E3%83%BC%E3%82%AD%E3%83%BC&__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&ref=nb_sb_noss_1 HTTP/1.1
Host: www.amazon.co.jp
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: */*
上では「k=%E3%83%93%E3%83%BC%E3%83%95%E3%82%B8%E3%83%A3%E3%83%BC%E3%82%AD%E3%83%BC」のようにパーセント記号(%)が入った文字列になっていますが、これは「ビーフジャーキー」をUTF8文字列としてURLエンコードしたものです。
つまり、ブラウザが「ビーフジャーキー」という文字列を勝手にURLエンコード(※2)してから、Amazonのサーバに送信している、ということになります。
URLエンコードされた文字列を受け取ったPHPは、それを元に戻してから使います。
※2ブラウザが勝手に変換してくれていますが、この変換は多くの場合文字化けの原因にはならないと思います。
少し話が逸れましたが、HTTPリクエストの中には間接的に日本語を含めることができる、ということになります。POSTの場合も基本は同じで、リクエストの形式が少し変わるだけです。
難しくなってきました、、、大丈夫か、、、!?
mbstring.encoding_translationの設定はどこで働く?
HTTPリクエストの中に日本語(正確にはURLエンコードされたマルチバイト文字列)を含めることができることが分かりました。
この日本語は、最終的にはPHPに渡されるのですが、mbstring.encoding_translation=ONの設定は、その文字列がPHPでの処理が開始される直前に効いてきます。
具体的には、リクエスト中にマルチバイトの文字列が存在していると、その文字列のエンコーディングを自動的に検出して、PHPの内部文字エンコーディングに変換しやがります。
内部文字エンコーディングが何か、という話も長くなりそうなので別で調べてもらうとして、重要なのは勝手に文字エンコーディングが変換されてしまうという点です。
この機能がONになっていると、例えば「ブラウザはSJISで送信しているのに、PHPではなぜかEUCで受け取っている」なんていう現象が起こります。さらに「UTF8で送ってもEUCで受け取ってしまう」という不可解な現象となります。
こんな感じ(SJISで送信した物がEUCになっているケース)。
まとめ
そんなわけで、原因不明の文字化けというか、今回の場合は厳密にはリクエスト中の文字列の文字コードがPHPで処理される際に異なる文字コードに変換されているという不思議現象が起こっていました。
その原因が、php.iniに含まれているmbstring.encoding_translationという設定がONになっている事でした。
そもそもこの設定をONにしなければならない状況というのがよく分からないのですが、移行元の開発元にもいろいろな事情があるのでしょう。
同じような現象に悩まされている人がいたら(いないと思いますが)この記事が役に立てば幸いです。
コメント