PHPで Shift-JIS使うなってことは前から聞いてたんで、今まで使ってなかったんですが、今回は携帯向けってことで仕方なくHTML側でシフトJIS使うことになりました。
環境としては下記のような感じです。
MySQLには MySQL Administrator を使ってテストデータを入れています。
問題に気づいたのは下記のような HTML 側のテキストエリアに入れた文字をDBで検索する部分を作っていたときでした。
id="order" action="find.php" method="GET">type="text" name="searchname" size="10" maxlength="100" >
type="submit" name="search" value="検索">
$findStr = trim(urldecode($_GET["searchname"]));echo "入力データ:" . $findStr . "
";//DB接続省略
mysql_query('SET NAMES sjis');
//クエリ生成
$query = sprintf("SELECT * FROM tablename WHERE name LIKE '%s' ",
mysql_real_escape_string($findStr ));
//クエリ実行
$result = mysql_query($query);
while ($row = mysql_fetch_assoc($result)) {
echo $row["store_name"] . "
";}
で、DBに MySQL Administrator で name列に文字化けやすい代表文字である "表" という文字をいれ、上記のHTMLのテキストボックスに "表" といれ検索しました。
本来ならヒットしないといけないのに、ヒットしません。
いろいろググって見るとマジッククォートっていうものがあるようです。
詳細は後ほど書くとして、これはOFFになってるので、関係なさそうです。
おそらく Shift-JIS が悪いんだろうってことは予想ついてるんですが、それをはっきりさせるために、HTML側を EUC-JP にし、SET NAMES も EUC-JP(ujis) にしてやってみました。
すると、ちゃんとヒットします。
やはり Shift-JIS が関係してますね。
条件追加した後のクエリとかも除いてみましたが、特に問題なさげです。
さらにググるとDBへのデータ挿入で文字化けっていう事例が多いので、ためしに INSERT するクエリを作ってみました。
$findStr = trim(urldecode($_GET["searchname"]));echo "入力データ:" . $findStr . "
";//DB接続省略
mysql_query('SET NAMES sjis');
$query = sprintf("insert into tablename ( name ) values ( '%s')",
mysql_real_escape_string( $findStr ));
$result = mysql_query($query);
echo mysql_error();
これで、HTMLに"表"って入れてPHPからDBに登録してみました。
すると、下記のようなエラーが。。。
You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for
the right syntax to use near ''表\' , 1 )' at line
やっぱり、MySQL内でのクエリ実行時には勝手にエスケープされてます。
SET NAMES と mysql_set_charset は内部的に違う処理をしてるようです。
そこら辺は、symfony × MySQL × Shift_JIS: 0×5c関連:で詳しく説明されてました。
簡単に言うと、mysql_set_charset()を使った場合はmysqlコネクションオブジェクトに文字コードを設定するため、Shift-JISの2バイト目の0x5cをエスケープしたりしないようです。
MSN相談箱:MySQL5.1の文字化けにも SET NAMES ではなく mysql_set_charset() を使うように提案されています。
ただ、今回使うレンタルサーバはPHPのバージョンが5.1のため mysql_set_charset() が使用できません。
ということで、どっかで書いていたようにGETやPOSTの値を別の文字コードに変換して、SET NAMES のその文字コードでやってやるという方向に落ち着きました。
こんな感じです。
$findStr = mb_convert_encoding(trim(urldecode($_GET["searchname"]) , "EUC-JP", "SJIS"));//DB接続省略
mysql_query('SET NAMES ujis');
$query = sprintf("insert into tablename ( name ) values ( '%s')",
mysql_real_escape_string( $findStr ));
$result = mysql_query($query);
echo mysql_error();
ただ、上記の場合 Shift-JIS から EUC-JP に変換してますが、これを UTF-8 に変換するときはまた注意が必要のようです。
その場合は、"SJIS" では無く "sjis-win" を使うのがベータみたいですね。
mb_convert_encoding( $string , "UTF-8", "sjis-win");
ここら辺の話は、下記サイトに乗ってました。
-OASIS- - 今日のメモ「mb_convert_encodingの文字化け(PHP)」
地方で活動するweb制作者の日々を綴るblog:PHPで UTF-8←→SJIS の変換を行う場合の注意
( しゃいん☆のブログ ):[php] mb_convert_encoding と UTF-8 の誤変換問題
さて、途中でチラッと出てきたマジッククォートですが、これが有効になってると Shift-JIS の場合さらにややこしくなるようです。
(PHP.ini では magic_quotes_gpc という項目で設定できます。)
レンタルサーバによってはこれが on になっているところが多いんですよね。(まぁセキュリティ対策といえば確かにそうかもしれません)
マジッククォートが有効になっていると GET,POST,Cookie でのユーザからサーバに来る文字の中に、'(シングルクオート)、" (ダブルクオート)、\(バックスラッシュ) 、NULL があれば自動的にバックスラッシュでエスケープするようです。
で、HTML側がShift-JISだと、文字の2バイト目の値が 0x5c だと、これをバックスラッシュとして勘違いし、勝手にエスケープ(バックスラッシュをさらに加える)するようです。(文字コード 0x5c 単体は\と定義されてるため)
たとえばHTMLで "表"(0x 95 5c) と打ってサーバに送信すると、PHPで受けたときには、"表\"(0x 95 5c 5c)となっちゃうわけですね。
対処法としては、Magic Quote GPC機能をoffにする、文字コードにShift-JISを使わない、受信した文字列に「\」を取り除く処理を行う があるようですが、今回の場合だと、前二つは要件、サーバ環境的に無理なので、最後の3つめを行うことに。
下記のように Magic Quote GPC機能が有効かどうかを見て、有効なら余計なバックスラッシュをのけるため stripslashes() を走らせばいいようです。
if(get_magic_quotes_gpc()) {$inputStr = stripslashes( $inputStr );
}
これはサニタイズと同じように関数化して必ず書くようなクセつけとくといいかもしれませんね。
ここら辺の詳細は、第3回 PHP と Shift-JIS 環境での文字化けについてに詳しく載っています。
しかし、ほんと文字コードがらみは厄介ですわ。。。。