長いけど我慢してくれ。
(ノ・・)ン。。。。。。(((●コロコロッ
PHPをはじめた頃や、
人から預かったシステムを触った時などで、
大体悩まされるのがこれ。
文字化け
文字化けが何かというのはご存じのとおり。
PHPにおいて文字化けが起こる理由は
勘違い・思いこみ
これ以外に存在しない。
(゜Д゜)ハァ?ナニイッテンノ?
などと思っている時点で貴方は文字化けと向き合っていない。
内部でどういう動きをしているのか正確に把握していない状態で利用するがために、
変換元変換先エンコードを勘違いして、
結果的に文字化けが発生する。
例えばmbstring.internal_encodingパラメータ。
これを「内部エンコード」と説明しているのを多々見受けられるが、
そもそもPHPに内部エンコードなどという概念は存在しない。
初心者本には大抵「嘘」が書かれている。
これは内容をわかりやすくするためには必要な措置ではあるが、
それを誤解したまま作業に関わるとスパイラルに陥ることになる。
結論から言おう。
どうすれば文字化けが起こらないようになるのか。
それは
自動変換を利用しない
ことだ。
変換の基準となる文字コードを一つ設ける。
文字コードの変換はすべてプログラム上で行い、
自動変換させるのは極力無くす。
これが最も効果的な方法。
自動変換とはhttp_input、http_outputの様なフィルター系や、
default_charsetの様な付加系(?)のようなもの。
これらを利用しないことが重要なのである。
もちろんこれは非常に煩わしい作業だ。
GETやPOSTのデータも一つ一つ変換しなければならないことになる。
(always_populate_raw_post_data = Onで、
$HTTP_RAW_POST_DATAを変換するという手もあるが)
全部手動が煩わしいというのならば、
自動変換される過程を理解した上で利用する必要がある。
だが、私も含めてほどんどのPHPプログラマにはかなり難しい内容だと思う。
マニュアルにはすべての記載があるわけではない。
わからない時はCのソースコードをたどるわけだが、
それはもっと煩わしいことではないだろうか?
私は自動変換は行わない方が良いと思っているし、
今でも自動変換は全く使っていない。
なので、今では文字化けする機会はほとんどなくなった。
(絵文字やソフトバンクと戦うことは今でもあるが。。。)
PHPの説明書にはマルチバイト系はこう設定しろと書いてある。
;; Disable Output Buffering
output_buffering = Off
;; Set HTTP header charset
default_charset = EUC-JP
;; Set default language to Japanese
mbstring.language = Japanese
;; HTTP input encoding translation is enabled.
mbstring.encoding_translation = On
;; Set HTTP input encoding conversion to auto
mbstring.http_input = auto
;; Convert HTTP output to EUC-JP
mbstring.http_output = EUC-JP
;; Set internal encoding to EUC-JP
mbstring.internal_encoding = EUC-JP
;; Do not print invalid characters
mbstring.substitute_character = none
しかし、ほとんどの場合こう設定すると混乱を極めることになる。
混乱しないようにするにはこう設定すべきだ。
;; Disable Output Buffering
output_buffering = Off
;; Set HTTP header charset
; default_charset = EUC-JP
;; Set default language to Japanese
mbstring.language = Japanese
;; HTTP input encoding translation is enabled.
mbstring.encoding_translation = off
;; Set HTTP input encoding conversion to auto
mbstring.http_input = pass
;; Convert HTTP output to EUC-JP
mbstring.http_output = pass
;; Set internal encoding to EUC-JP
mbstring.internal_encoding = EUC-JP
;; Do not print invalid characters
mbstring.substitute_character = none
mbstring.detect_order = SJIS,EUC-JP,JIS,UTF-8,ASCII
ようするに
ほとんどOFFで良いと言うことだ。
細かい設定はphp.iniではなく、
.htaccessかプログラム内部で行った方がよい。
ではなぜこの方が良いのか順を追って説明しよう。
[More:]
●default_charsetはデフォルトの文字コードのことではない。
非常に誤解しやすい内容。
default_charsetというパラメータはご存じの人も多いと思う。
それに大抵の初心者本にはこれを設定するように書いてあるが、
むしろ逆である。
default_charsetとは
出力時にHTTPヘッダとして送信する文字コード名
のこと。
これを指定しておくと以下のコードが自動で出力される。
default_mimetype = 'text/html'で
default_charset = 'utf-8'の場合
header('Content-Type: text/html; charset:utf-8');
PHPは何もWebだけ取り扱うわけではない。
Webもあればメールもあるし、
CSVやXMLもあれば、
コマンドラインもある。
そんな状態でこんなコードが自動で送信されれば迷惑この上ない。
なので、default_charsetは使用すべきではないのである。
追記:
ただし、自動でやってくれる機能を除去したので、
手動でheader関数でContent-typeを打ち込むこと。
それからHTMLにはmetaで文字コードを指定する。
(xhtmlなら先頭行も)
・コメントでの指摘により追記
default_charsetを打たないとセキュリティーホールが発生するのでは?
http://gihyo.jp/dev/serial/01/php-security/0011
という意見だが、
HTTPヘッダーでContent-typeを打つ必要があると言うことであって、
default_charsetを指定せよと言うことではないと思われる。
前述の通り、
default_charsetは「自動でContent-type」を打つ機能なので
これはむしろ邪魔。
(しかも、名前から機能が想像できない。)
しかし、自動でやってくれる機能を外したので、
手動で補完する必要がある。
システム出力時にheader関数でContent-typeを打つこと。
metaで十分とするかは制作者の裁量だと思うが、
両方しておくことをオススメする。。
●languageパラメータは内部言語のことではない
知っている人は少ないかもしれないが、
languageパラメータは全く使われないパラメータだ。
これが利用される機会はただ一つ。
mb_send_mailを利用した時だけ
mb_send_mailは全く使わない関数なので、
このパラメータは存在意義がない。
mb_send_mailを使わない理由は後述しよう。
●internal_encodingは内部エンコードのことではない
先ほどの例の時にも書いたが、
PHPには内部エンコードという概念は存在しない。
ではmbstring.internal_encodingとは何なのか。
これは
mbstring関数のデフォルトエンコード
なだけである。
mb_convert_kanaなどのパラメータで、
変換元文字コードの指定がなかった時にだけ利用されるものだ。
これが必須なのは
mb_decode_mimeheaderぐらいだ。
( ・_・;) エ゛ッ!?
驚くのも無理はない。
大抵のPHP本にはPHPの内部エンコードと説明しているし、
インストール直後に必ず設定するように書かれている。
だが、実際はほとんど使われていないパラメータなのだ。
例えばPHPのソースコードをEUC-JPで書いたからといって、
mbstring.internal_encodingがEUC-JPである必要は全くない。
SJISで利用した場合、
5C問題(「ソ」「表」などを記述するとパーサエラーが出る)が発生するわけだが、
internal_encodingをSJISにしても問題は解決しない。
これを解決するには--enable-zend-multibyteをつけてコンパイルし直し、
php.iniの設定で
mbstring.script_encoding = Shift_JIS
とする必要がある
(もちろんSJISは利用しないのが一番いいのだが。)
しかし、変換元が固定になるというのは重要なことなので、
これはソースコードと揃えておくのがBetter。
マルチバイト関数を使う時は変換元エンコードを必ず指定する癖を付けておくのが重要なのである。
●detect_orderは必ず指定する
detect_orderとは
文字コードの自動検出の優先順位
のこと。
何も指定しなければ「auto」というパラメータになるのだが、
このautoはかなりくせ者だ。
何も指定していない場合や、autoを指定すると「ASCII,JIS,UTF-8,EUC-JP,SJIS」の用に展開される。
が、マルチバイトを計る上でASCIIが一番先頭である時点ですでに欠陥である。
detect_orderは二次的に利用される機会が多いため、
必ず指定する必要がある。
Webで出力する文字コードを先頭にしてJISやASCIIは最後の方にするのが良い。
これはプログラム上でも構わない。
私はプログラム上で指定している。
可能な限りphp.iniの設定に依存しないためだ。
●http_outputは利用しない
http_outputについては二つ勘違いされやすいポイントがある。
一つは「指定すると自動で変換されて出力される様になる」
Σ( ̄ロ ̄; エッ!?チガウノ?
http_outputは指定するだけでは何も動作しない。
これは
mb_output_handlerが実行された時の出力エンコード
に過ぎない。
ob_start("mb_output_handler")
と指定されて初めて使われるのだ。
もう一つは「すべての出力に適用される」
( ・_・;) エッ!?チガウノ?
ob_startを実行しても、
すべての出力に適用されるわけではない。
http_outputの発動条件は
出力がtext/htmlである
必要がある。
なのでapplication/xmlとかをヘッダーで投げていると動かないのだ。
●mb_send_mailは使うな
マルチバイトのメールを送るからといって、
mb_send_mailを使っている人も多いかもしれない。
だが、このmb_send_mailは文字化けの元凶だ。
mb_send_mailは何の文字コードを何の文字コードに変換するかを指定することが出来ない。
なので、何の文字コードで入力を受け付けて、
ブラックボックスを通った後に何の文字コードで出力されるか全くわからないのである。
理由はmb_send_mailで利用される文字コードは、
languageパラメータに依存しているからだ。
languageにJapaneseと指定したところで、
ISO-2202-JPで送るのか、UTF-8で送るのか、またはSJISで送るのか全く不明だ。
それにマルチパートメールの場合は、
文字コードが一律でないことがある。
例えばDoCoMoのデコメール。
テキスト部分はJISだがHTML部分はSJISだ。
DoCoMOはSJIS Loveなためこのような仕様になっているが、
迷惑この上ない。
そんなことをせずに、
送信するメールをあらかじめ変換しておきmailに通す方が制御がわかりやすい。
mailも使い勝手が悪い関数なので、
PEAR::MailでSMTP送信するのが一番良い方法だろう。
ちょっと調べただけでもこれだけあるのだ。
すべてを理解している人はマルチバイト関数を開発した人ぐらいだろう。
ならば、そんなものは利用しない方が良い。
自動で行われるものを手動でやるのだから、
オーバーヘッドが発生するのはやむを得ない。
だが、そんなものは微々たるものだ。
それで負荷が上がることはまずない。