PHPの文字化けを本気で解決する

PHP, 絵文字, XML, 文字コード, 文字化け, SJIS, EUC-JP, UTF-8

09:00:36, by admin Email , 339 words, 236158 views   Japanese (JP) del.icio.us

長いけど我慢してくれ。

(ノ・・)ン。。。。。。(((●コロコロッ

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送信するのが一番良い方法だろう。




ちょっと調べただけでもこれだけあるのだ。
すべてを理解している人はマルチバイト関数を開発した人ぐらいだろう。
ならば、そんなものは利用しない方が良い。
自動で行われるものを手動でやるのだから、
オーバーヘッドが発生するのはやむを得ない。
だが、そんなものは微々たるものだ。
それで負荷が上がることはまずない。

Trackback address for this post:

http://hain.jp/htsrv/trackback.php/125

Comments, Trackbacks:

Comment from: g [Visitor] · http://hain.jp/
スゲー参考になった。
ありがとう!
PermalinkPermalink 2007/02/14 @ 14:34
Comment from: Yoshi [Visitor] · http://clickyourstyle.com
便利そうな機能っていうのは未練が残って、
使いこなせないのに手放せないことがよくあります。
経験豊富な方の切るところばっさり切る知識ってすごいなと、いつも思います。
複雑な機能を作るのは簡単ですけど、
シンプルにするのは難しいです。
助かりました。

#うちは素人&PHP&レンサバで動いてます。サーバを変えるたびにphp.iniが違って.htaccessに追加してたりします。
PermalinkPermalink 2007/02/15 @ 09:38
Comment from: y [Visitor] · http://selfkleptomaniac.org
125
PermalinkPermalink 2007/02/15 @ 14:08
Comment from: admin [Member]
> gさん

ども。
ご自身でじっくり検証してください。

> Yoshiさん

PHPには余計なお世話機能も結構あるので、
必要なものだけ選んで使うのがいいかもです。

> yさん

125って何だろうと思っていたのですが、
http://selfkleptomaniac.org/archives/38
を読んで納得しました。
ツールの不具合かな?

ご指摘の通りinternal_encodingで影響するのはmb_decode_mimeheaderの方ですね。
PermalinkPermalink 2007/02/16 @ 03:27
Comment from: 2% [Visitor]
大変勉強になりました。
それに、かなり読みやすい。
リスペクトです。
PermalinkPermalink 2008/02/06 @ 17:14
Comment from: admin [Member]
ありがとうございます。
内容を鵜呑みにせず、
ご自身で検証してからご利用ください。
PermalinkPermalink 2008/02/07 @ 03:06
Comment from:   [Visitor]
default_charsetを指定しないと
セキュリティホールになると書かれている
ページもあるのですが、大丈夫なのでしょうか?
PermalinkPermalink 2008/02/13 @ 21:59
Comment from: admin [Member]
「はい、大丈夫です。」とはさすがにいえませんが。

ご指摘のものは以下のサイトのことでしょうか?
http://gihyo.jp/dev/serial/01/php-security/0011
(違ったらごめんなさい)
しかし、これではdefault_charsetを指定しないとセキュリティホールになるという説明にはならないと思います。
理由は文中にある
[アプリケーションを作る場合,必ず文字エンコーディングを明示的にスクリプトから設定するようにします。]
がイコール
[default_charsetを指定しなければならない]
にならないからです。
それはheader関数の役割です。
このあたりは追記しておきます。

ご指摘ありがとうございました。
PermalinkPermalink 2008/02/14 @ 17:24
Comment from: だにえる [Visitor]
今まさにこの問題で頭を悩ましていたのですが、ここで書かれてる設定に変更したら解決しました!!
本当にありがとうございます!!
PermalinkPermalink 2008/10/06 @ 20:10
Comment from: shingo [Visitor]
ぐぐって来ました。
「すべてを理解している人はマルチバイト関数を開発した人ぐらいだろう。」のくだり、もちろん自身は開発してませんが、
裏事情が分かったような気がして\(^o^)/でした!
ありがとうございました!!!
PermalinkPermalink 2009/01/20 @ 12:21
Comment from: ツッタカ [Visitor]
おかげさまで、PHPの文字化けの問題が一挙に解決しました。
PHPからApacheへ送るファイルも、SJIS、EUC、UTF 自由自在に使えるようになりました。
PermalinkPermalink 2009/08/23 @ 10:37
Comment from: yohgaki [Visitor] · http://blog.ohgaki.net/
紹介されているgihyo.jpの記事の著者です。

昔は確かに自動エンコーディング変換を使うような設定例が多かったですね。output buffer以外は例示されている設定で良いと思います。あれは無効にする意味はあまりないです。

default_charsetについては設定すべきだと思います。何をデフォルトにするのか?は結構難しくて、JavaのWeb環境では入力が全部ISO-8859-1になっていてバイナリ変換が必要、テキストでもバイナリに変換→UTF-8に変換ということしなければならなかったりします。

例えば、すべてをテキストとして処理した場合、通常の場合は数少ないと思われるバイナリデータだけを例外として処理すればよくなります。

PHPの場合は入力データはすべてバイナリとして処理されているのでJavaに比べてアプリが脆弱になり易いので入力バリデーションで徹底してチェックする方が良いです。(mb_check_encoding関数)

Webの場合、ほとんどがテキストです。現在ではUTF-8がほとんどでしょう。text/* のタイプはデフォルトをUTF-8として取り扱い、例外だけ例外として処理する方が安全かつ確実です。

ob_start()でバッファリングしていれば、default_charsetはいつでも変更できるので例外を例外として扱うことに支障はありません。

エラー処理も簡単になるので、まず最初にob_start()をすると便利です。エラーが発生したらバッファを消して、エラーページを出力すれば良いからです。エラー・例外ハンドラで実装すれば自動で処理できます。

メールは指摘の通りです。mb_send_mail()はlangauge設定に合わせて、ボディのエンコーディングを決め打ちします。その割に追加ヘッダのエンコード処理はしてくれません。複雑なメールを送る場合はmail()を使って、作った方が良いです。

PermalinkPermalink 2010/10/01 @ 19:01
Comment from: 珀夜 [Visitor] · http://www.netcircus.jp/~hakuya/
色々調べていたら流れ着きました。
いや、ホント助かりました・・・charsetと出力文字の不一致が一気に解決。目から鱗です
PermalinkPermalink 2010/11/18 @ 23:30

This post has 1 feedback awaiting moderation...

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))
This is a captcha-picture. It is used to prevent mass-access by robots.

Please enter the characters from the image above. (case insensitive)

powered by b2evolution

shinobi

Neighbors
Relative
Favorites
PR

極論istの技術屋を始めて早幾年。 流れの速い業界の波にもまれながらも精一杯生きている様をとくとごらんあれ。

Archives
スポンサー

Latest bookmark
Search

Categories

Who's Online?
Misc
Syndicate this blog XML

Valid XHTML 1.0! Valid CSS! Valid RSS 2.0! Valid Atom 1.0!