概要
PHPで全角を扱う場合、少し加工したり情報を取得するためにもマルチバイト対応されている関数かどうかを意識する必要がある。
今回は、最近私が遭遇した、全角スペースで文字を配列に変換する処理での注意点をまとめる。
とりあえず、問題のあるソースコード
名前を全角スペースで姓と名に分割する処理を、EUC−JPで処理した場合のソースコード。
(文字コード変換周りの処理が少しごちゃごちゃしているのはご愛嬌で・・・)
mb_internal_encoding('EUC-JP');
$fp = fopen('result.txt', 'w');
$nl = "\n";
$space = ' '; // これは、全角スペース
$space = mb_convert_encoding($space, 'EUC-JP', 'UTF-8');
$name = '伊集院 光';
$name = mb_convert_encoding($string, 'EUC-JP', 'UTF-8');
// === まずは、そのままの文字列を文字コードとともに出力する
fwrite($fp, $name. $nl);
for ($i = 0; $i < mb_strlen($name); $i++) {
$char = mb_substr($name, $i, 1);
fwrite($fp, $char. ' ('. bin2hex($char). ')'. $nl);
}
// === explodeで配列に変換した結果を出力する
fwrite($fp, '----------'. $nl);
foreach (explode($space, $name) as $value) {
fwrite($fp, $value. ' ('. bin2hex($value). ')'. $nl);
}
実行結果は以下のとおり。
伊集院 光
伊 (b0cb)
集 (bdb8)
院 (b1a1)
(a1a1)
光 (b8f7)
----------
伊集� (b0cbbdb8b1)
仝� (a1b8f7)
「院」の後半1byteのa1と全角スペース前半のa1で全角スペースとして処理され、分割結果が文字化けしてしまう。
正しいソースコード
explode
ではなく、mb_split
を利用して分割処理を修正した。
mb_internal_encoding('EUC-JP');
mb_regex_encoding('EUC-JP');
$fp = fopen('result.txt', 'w');
$nl = "\n";
$space = ' '; // これは、全角スペース
$space = mb_convert_encoding($space, 'EUC-JP', 'UTF-8');
$name = '伊集院 光';
$name = mb_convert_encoding($string, 'EUC-JP', 'UTF-8');
// === まずは、そのままの文字列を文字コードとともに出力する
fwrite($fp, $name. $nl);
for ($i = 0; $i < mb_strlen($name); $i++) {
$char = mb_substr($name, $i, 1);
fwrite($fp, $char. ' ('. bin2hex($char). ')'. $nl);
}
// === mb_splitで配列に変換した結果を出力する
fwrite($fp, '----------'. $nl);
foreach (mb_split($space, $string) as $value) {
fwrite($fp, $value. ' ('. bin2hex($value). ')'. $nl);
}
mb_split
は、mb_regex_encoding
で設定されている文字コードで分割処理を実行するため、正しく分割処理が行える。
実行結果は以下のとおり。
伊集院 光
伊 (b0cb)
集 (bdb8)
院 (b1a1)
(a1a1)
光 (b8f7)
----------
伊集院 (b0cbbdb8b1a1)
光 (b8f7)
まとめ
今回のケースは、おそらく元々は半角スペースで区切っていたものを、仕様変更か何かで全角スペースを使うようになり、explode
の引数だけを変更して対応したことで発生した問題だと思われる。
文字列の中に全角が含まれていても、区切り文字が半角文字なら、explode
でも問題なく動作する。これは、EUC-JPでは半角文字のコードが21〜7Eまでで、全角文字の後半1byteはA0以降のため、誤検知する恐れがないため。
ただし、全角文字を区切り文字に指定した場合には上記のような誤検知が発生する可能性があるため、きちんと使用している関数がマルチバイト対応しているかどうか確認しておく必要がある。
ちなみにSJISの場合
SJISだと、全角スペースの文字コードが8140
のため同じ現象は発生しない。
EUCの時と同様に前半と後半が同じ並びになる文字は「=(8181
)」なので、全角スペースの代わりに=を、お名前も全角スペースの直前が81
になるものを利用すると、同様の現象を発生させることができた。
mb_internal_encoding('SJIS-win');
mb_regex_encoding('SJIS-win');
$fp = fopen('result.txt', 'w');
$nl = "\n";
$space = '=';
$space = mb_convert_encoding($space, 'SJIS-win', 'UTF-8');
$string = '宇梶=剛士';
$string = mb_convert_encoding($string, 'SJIS-win', 'UTF-8');
// === まずは、そのままの文字列を文字コードとともに出力する
fwrite($fp, $string. $nl);
for ($i = 0; $i < mb_strlen($string); $i++) {
$char = mb_substr($string, $i, 1);
fwrite($fp, $char. ' ('. bin2hex($char). ')'. $nl);
}
// === explodeで配列に変換した結果を出力する
fwrite($fp, '----------'. $nl);
foreach (explode($space, $string) as $value) {
fwrite($fp, $value. ' ('. bin2hex($value). ')'. $nl);
}
宇梶=剛士
宇 (8946)
梶 (8a81)
= (8181)
剛 (8d84)
士 (8e6d)
----------
宇� (89468a)
″ьm (818d848e6d)