PHPと日本語文字列


日本語圏で使うWebアプリをPHPで組み立てる上では、日本語処理は欠かせません。では、どのように処理していけばいいのでしょうか。

文字コードはUTF-8がおすすめ

まず、内部処理で使う文字コードとしては、UTF-8で進めるべきでしょう(特定の文字コードを要求するシステムとのやり取りの際には、入出力時に変換すれば済みます)。シフトJISやEUCでは文字の切れ目がわかりづらく、いわゆる「ダメ文字」問題など、妙なところにヒットする事故が発生しがちです。これに対して、UTF-8の場合は

  • 1バイト文字…0x00-0x7F(0b0xxxxxxx)
  • 複数バイト文字の2バイト目以降…0x80-0xBF(0b10xxxxxx)
  • 2バイト文字の1バイト目…0xC21-0xDF(0b110xxxxx)
  • 3バイト文字の1バイト目…0xE0-0xEF(0b1110xxxx)
  • 4バイト文字の1バイト目…0xF0-0xF72(0b11110xxx)

というように、「1バイト目」と「それ以降」が別に定義してあるため、単純なバイト比較による検索でも文字の切れ目を誤って誤爆することはありません(正規化の加減で「同じ」に見える文字がヒットしないことはありますが、それとは別問題です)。

さらに、この特徴があることで、シングルバイトを意識した関数を使える場面も増えますし、正規表現のPCREもUTF-8モードで使うことができます。

PCREとUTF-8

PHPのPCRE正規表現には、UTF-8指定子のuがあります。UTF-8指定子を付けずに正規表現を書くと、UTF-8の1文字はそのままバイト列として認識されます。

そのままで問題なく動く場面も多いですが、.1バイトにヒットしますし、2バイト以上の文字をキャラクタクラスの[]に入れてしまうと、バイト単位でどれかという意味になってしまい、正しくヒットしなくなります。よほど速度にシビアな場面を除けば、/regex/uというように、UTF-8指定を付けるようにしておいたほうがいいでしょう。

なお、u付きの正規表現で処理をかけると、検索対象がUTF-8として正しいかどうかのチェックをして、おかしなコードならヒットさせずにFALSEを返します。これを利用して、preg_match('//u', $target)と空の正規表現と照合させることで、UTF-8としての整合性チェックに使えます。

UTF-8での文字列関数

上に述べたように、UTF-8の文字列にバイト単位の比較で検索をかけても、文字の切れ目を誤ってヒットしてしまうことは起きないので、引数を文字列単位で扱うような関数(explodesprintfstr_replacestrposなど)は、UTF-8文字列に対しても問題なく適用することができます(strposはバイト数単位で位置を返しますが)。

一方で、文字列をバイト単位で扱う関数の場合、UTF-8の文字が途中で切れることになるため、正しく動作しません。例えば、trimで全角空白を除かせることはできませんし、str_shuffleはバイト単位でシャッフルして、(たいていの場合)正しくないUTF-8としてしまいます。strtrでは、2引数であればUTF-8でも問題ないですが、3引数の場合はASCII内の文字を、同じくASCII内の文字に置き換えるのにしか使えません(が、入力文字列自体はUTF-8混じりでも問題ありません)。


  1. 0xC0、0xC1とすると、計算上は1バイトで表せる文字を表現するコードとなりますが、そのような冗長なコードの使用は禁止されています。 

  2. もともと、ISO 10646としては31ビットコードを計画していたので、UTF-8では5バイト・6バイト文字(1バイト目が0xF8以降)も存在しうるのですが、もはやUnicode外の領域に文字が割り当てられることはなくなったので、使用されることはありません。