文字エンコーディング混在PHPページ

このエントリは2007/06/21の再掲です

ひとつのロケールに複数の文字エンコーディングがある、日本語のような言語では、ひとつの動的Webページを書くだけでも、部分ごとにエンコーディングの向き不向きがあります。多くのWebブラウザがShift_JISを好むことから、出力をShift_JISのみと限定された場合、ページレイアウトのデザイナとコード実装者で、使いたいエンコーディングが変わってきます。

デザイナにとっては、出力がShift_JISの場合は、METAタグのContent-TypeにShift_JISと書いておくのが一番作業しやすいでしょう。ただ、それをもとにコードのエンコーディングを自動判別してしまうエディタが多々あるため、HTMLのコードもShift_JISで書きたくなります。

いっぽう、プログラム実装者はShift_JISでコーディングできないので困ります。*NIX文化で育ったPHPは、プログラムコードにShift_JISを含めることができません。<?php ?>の外にあるHTML中のShift_JIS文字は、ありのままのバイト列として出力されますが、PHPコードの中に出現する文字列リテラルShift_JIS文字を使うと、2バイト目に出現するバイトがエスケープ文字と文字コード一致するとき、構文パーサが文字化けを引き起こします。

<?php
$hoge."の表";
?>

と書くと文字列が閉じません。

この二者はおそらく、別のファイルに分けて作業したものを結合させるでしょう。さあ、Smartyを使いますか?いえいえ、P言語業界には、あんなヘビーウェイトなものをインストールする必要/権限などない仕事のほうが多いんですよ。多くの場合、ページレイアウトの一部にを挿入し、そこから決まった名前の関数を呼び出すのが最適解のはずです。

さてこのときよく起こる問題は、最後まで二者が同じ文字コードを使いたがらなかったとき(むしろ一致するほうが珍しい)です。プログラマShift_JISでコーディングさせ、いちいち2バイト目を気にしながら、丁寧に\記号を挿入させますか?デザイナの表示テスト作業に、毎回文字コード変換を強制しますか?それとも、Shift_JIS出力をあきらめるよう顧客を説得しますか?

どれもすべて、避けたい道です。できれば、顧客を説得する苦労をこれ以上増やさず、ほとんどのHTMLはデザイナがShift_JISで書き、ダイナミック要素についてはUTF-8EUC-JPで実装しながらテストしたいですね。そんなことできるんでしょうか?

答えは、できる、です。出力バッファリングを使い、二者の境界で文字コードを切り替えます。たとえば、EUC-JPの出力を行うコードをShift_JISのページから利用する場合はこんな具合です。

<?php
ob_start(create_function('$str',
 'return mb_convert_encoding($str,\'SJIS\',\'EUC-JP\');'
));
ob_implicit_flush(false);
hoge(); //この関数はEUC-JPで出力します。
ob_end_flush();
?>

この手法を一般化したものをダウンロード可能にしておきました。より詳しい理解のためには、ソースを読んでハックしてください。

ダウンロード jcoderegion.zip

たしかに、コードパートが生成する出力文字列は、レンダリングに2倍のコストがかかります。が、Smartyが5〜20倍のコストをページ全体にかけてしまうことを思えば、かわいいものではないでしょうか?

出力バッファリングはネスト可能なので、php.iniやもっと上の構造で何らかの出力バッファリングをすでに行っていた場合でも、上位構造にいっさい影響を与えることなく、閉じたスコープ内で処理が完結します。新しいことをやるたびに不安の負債が溜まるPHPですが、この手法はわりと安心できる類のものです。

バッファリングについておまけ情報。PHPプログラミングではよく、「プログラムの一番最初でないと、HTTPヘッダ情報を書き出すことができない」という問題が発生します。これ、「ヘッダ情報の処理がすべて完了するまで、すべての出力をバッファリングしておく」という回避テクニックがマニュアルに書いてあるのをご存知ですか?この問題の原因は、少しでもHTML本文パートを出力してしまうと、もうHTMLヘッダに戻って書きなおせないためなのです。でもバッファに貯めておけば、HTML本文パートが出力されたことにならないんですね。ついでにちょっと知っておくと便利かも。