文字化けしないはずの仕組みで文字化け

メールフォーム作成の仕事で文字化け問題に直面した(文字化けなんてすごく久しぶり)。メールフォーム自体はすでにmojavi2で作成したベースがあるので、何のことはない。「超簡単」のはずだった。しかし文字化けが発生してしまった・・・。ちなみに今回の環境はスクリプトはEUC-JP、HTMLはSJIS。

最初は何が問題なのかわからなかった。だって文字化けしたことがなかったから。たいていのサーバでPHPのマルチバイト関連は何も設定されていないから、問題なかったのかもしれない。今回はサーバ側で既定の設定がしてあったことが原因のようだ。

問題解決のお決まりの手法だが、まず表示されたソースの文字コードをEUC-JPやUTF-8にしてみた。しかし、それでもうまく表示してくれない。どの文字コードにしてもうまく表示されないということは、間違った文字コード変換をしているか、もしくは元々のテンプレートが指定された文字コードでないか、どちらかだ。テンプレートをダウンロードしてみたがどうやら設定どおりになっている。ということは間違った文字コード変換をしているようだ。

間違った文字コード変換にも2種類ある。元の文字コードや変換先の文字コードを設定とは異なるコードに変換してしまうことと、文字コード変換を多重にしてしまう(1回でいいのに)ことだ。

このプログラムは他のサーバで正常に動いているところを見ると、間違った文字コード変換をしているとは考えにくい。ということは余計な文字コード変換をしてしまっていると考えた。

このプログラムでは、マルチバイトがらみの設定の影響を避けるため、プログラムの先頭で以下のように記述している。

mb_language(‘Japanese’);
mb_internal_encoding(‘EUC-JP’);
mb_http_input(‘pass’);
mb_http_output(‘SJIS’);
ob_start(‘mb_output_handler’);

まず原因は「ob_start(‘mb_output_handler’);」の記述だった。

実はこのサーバ、phpinfoで情報を見ることが出来ない(制限がかけてあるのだ)。いちおうお仕事の発注元経由で問い合わせをかけることは出来るが、それにしても時間がかかる。phpinfoを見ることが出来ないのは、問題解決をむずかしくする要因だった。ではなぜ上記の記述がNGなのか。

多分php.iniで「mb_output_handler」が設定されていて、そこで正しく変換しているのに、再度上記の記述で変換をかけたために文字化けしていたと思われる。
ということで、問題の行をコメントアウトして正しく表示されることを確認した。

しかし問題その2。入力した文字が確認画面で文字化けして表示される。もしやと思いencoding_translation(自動文字変換)が有効になっていないかチェックした。案の定、上記設定が有効になっていた。
プログラムとしては、SJISのHTMLから飛んでくるデータはプログラム内部で内部円コーディングに変換するように組んであるのだが、これが余計。自動変換されたものを再度変換しているのだ。で、その行をコメントアウト。

これで正しく表示されるようになった。
PHPプログラムの文字化け解決の教科書みたいな事例でした。

一時的に出力文字コードを変換する

PHPはその機能で出力を各種文字コードに自動変換して出力することができる。例えばLinux使いなプログラマが、社内システムなんかを構築する場合、通常のサイトやデータベースはEUCで作ったとしても何の問題もないが、CSVファイルをダウンロードさせるような仕組みを作る場合はShift_JISにテキスト変換してやらないと、誰もそのファイルをエクセルで開くことができなくなる。
以下のコードを出力に際して書き込んでやればよい。

mb_http_output(‘SJIS’);
ob_start(‘mb_output_handler’);

珍しく上記2行の意味を考えてみた。
1行目は出力をSJISにしましょう、ということだ。しかしそれだけではだめ。つまり「SJISにしましょう」ということではなく、「SJISにしたいです」という宣言でしかない。これを実行するのが2行目。
ob_startというのはバッファリングを有効にするための関数だ。つまりprint構文などが呼ばれた場合に逐一ブラウザに出力するのではなく、サーバ側で全てのPHP処理が完了するまで待ちましょう、という関数だ。で、その引数で指定されているmb_output_hander。これはいわゆるコールバック関数というやつだ。コールバック関数とは、親関数(ここではob_start)が呼ばれた際に、そのついでに処理してくれ、というような関数だ(突き詰めて書けば違うけど、おおむねこんな感じ)。
mb_output_handerはコールバック専門の関数でob_startによって出力バッファにためられたデータを、出力用文字コードに変換するという機能を持つ。ということで上記2行のプログラムは、1行目で「出力をSJISにしたい」と宣言し、2行目のob_startで、出力をいったんバッファにためる、と設定し、mb_output_handerでバッファ内の文字列を指定どおりに文字コード変換する、という処理をすることになる。
なおmb_output_handerはいろいろ条件があって、header関数でcontent-typeを出力していない、とか、もし設定されていた場合はtext/で始まるmimeが指定されている、とかある。もちろんhttp_outputがpassに設定されていたら変換のしようがないのは言うまでもない。
ということで諸々制約が多い。プログラム書き始めの人にとってはかなり使いにくい関数といえる(理屈を考えず、2行書いておけばいいという話もあるが・・・)。

phpでファイルのダウンロード

ウェブ閲覧者からアクセスできないところにあるファイルをダウンロードさせたり、データベース取得したデータをダウンロードさせたりするとか、PHPから動的にファイルを生成してダウンロードさせたい場合は多い。特に管理画面とかではそういう機能をつかうことも多いはず。
ダウンロードに関しては、基本的にプログラムで難しい処理をすることはなく、HTTPレスポンスヘッダで適切なヘッダを返してあげさえすれば、ブラウザがそのようにふるまってくれる。

<?php
header(“Accept-Ranges: none”);
header(“Content-Disposition: attachment; filename=file.txt”);
header(“Content-Transfer-Encoding: binary”);
header(“Content-Length: “. strlen($downloadFile) );
header(“Content-Type: text/octet-stream”);
print($downloadFile);
exit();
?>

とりあえずこれで大丈夫な模様。
Accept-Rangesはbyteを指定すべきかもしれない。
Content-Dispositionはinlineを指定するとおそらくブラウザにインラインで表示されるのだろう。filenameの指定はダウンロードダイアログで表示させるべきファイル名。
・・・そんな感じ。

Windows版でDOM XML関数を利用する

基本的にサーバはLinux(RedHat系)を使用しているけれど、ローカルの開発環境(OS:WindowsXP PRO)にもapache+PHPをインストールし、どこでも開発できるようにはしてある。
XMLを使用したいと思い、ローカルな環境に設定を試みたがつまずいた。現状phpinfoを見たけれど、XML関連の表示はあるけれどDOMXML関連の表示が見当たらない。これは「きっとphp.iniのコメントアウトしてあるDLLを有効にすれば」と思い、該当部分を有効にしてapacheを再起動した。
しかし再起動時にエラーが表示され「c:¥php¥extensions¥php_domxml.dllが見つかりません」と怒られる。確かにc:¥php¥extensions¥php_domxml.dllは存在するし、同じディレクトリにあるphp_mbstring.dllは正常に読み込むことができている。
「どうして???」とウェブで調べてみて結局マニュアルに行き着いた。マニュアルによると「dllsディレクトリにあるiconv.dllをシステムディレクトリに入れるように」とある。このファイルをc:¥windowsにコピーし、apacheを再起動すると今度は問題なくapacheが起動した模様。
phpinfoを見るとDOMXMLの表示があった。一件落着。

ファイルダウンロードプログラムでのトラブル

PHPで、ファイルを動的に生成してダウンロードさせるプログラムを作成したときに、以下のような問題が発生した。

○「対象をファイルに保存」するのは成功する
○「開く」でダイレクトに開こうとすると「ファイルが見つかりません」といわれる

PHPのMLに投稿があってわかったが、これはブラウザのキャッシュが原因とのこと。Microsoftのサイトに解決策がある。
http://support.microsoft.com/default.aspx?scid=kb;ja;436605


守谷市(まちの情報ポータル) 無料アンケートレンタルjpForm.net