PDOでMySQL接続したら文字化けした時のメモ

文字化け問題というのは何時の時代もエンジニアを苦しめる…ような気がします。

今回はPDO接続で文字化けが起こった時のメモです。

状況

DBもphpファイルもUTF-8な気がするのに、PDOでSELECTしてくると文字化けする。

(????になってしまう)

CREATE TABLEでも文字コードをきちんと指定した。

DBからとってきた文字列以外の日本語は普通に表示される。

phpのversionは5.3.3。MySQLは5.1.69。

DBの設定

/etc/my.cnf


# character-set
default-character-set=utf8
skip-character-set-client-handshake

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
default-character-set=utf8

PHPファイルの設定


-> % nkf -g test.php
UTF-8 (LF)


/etc/php.iniも見てみたけど問題無さそう…

対策

ググってググって対策してみました。

① DBの設定変更

こちらの記事を読みました。

PDOでmysqlに接続する時の文字コード | Bamboo lath 日々の記録

以下の様なコードで接続時の文字コードの状況を確認できるそうです。

<?php
 $PDO=NULL; try {  $PDO=new PDO(  sprintf('mysql:host=%s;dbname=%s','localhost','dbname'),'user','password',  array(  PDO::MYSQL_ATTR_READ_DEFAULT_FILE=-->'/etc/my.cnf',
 PDO::MYSQL_ATTR_READ_DEFAULT_GROUP=>'client',
 PDO::ATTR_EMULATE_PREPARES=>'FALSE'));
 $PDO->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);
} catch(PDOException $e) {
 var_dump($e->getMessage());
}

//$PDO->query("SET NAMES utf8");

$sth=$PDO->prepare("SHOW VARIABLES LIKE 'char%'");
$sth->execute();
while($ins=$sth->fetchObject()){
 echo $ins->Variable_name . " | " . $ins->Value . "\n";
}

試してみました。


-> % php dbtest.php
character_set_client | utf8
character_set_connection | utf8
character_set_database | latin1
character_set_filesystem | binary
character_set_results | utf8
character_set_server | latin1
character_set_system | utf8
character_sets_dir | /usr/share/mysql/charsets/


一部latin1になってる。これのせいかな?

MySQLを再起動してみます。

-> % sudo service mysqld stop
-> % sudo service mysqld start

再チャレンジ


-> % php dbtest.php
character_set_client | utf8
character_set_connection | utf8
character_set_database | latin1
character_set_filesystem | binary
character_set_results | utf8
character_set_server | utf8
character_set_system | utf8
character_sets_dir | /usr/share/mysql/charsets/


character_set_databaseが変わってないですね…

調べてみたところ、DROP DATABASEの必要がある…??

MySQLに日本語でINSERTできない場合 | たこはちの「へのかっぱ」日記

サービスはローンチしていなかったので、drop tableを決意しました。

ひとまずデータのバックアップを取ります。(参考:DBのバックアップと復元

そして drop table して再 create table。

これで全てがutf8に。(character_set_filesystem は binaryのままでもOKみたいです)

そして意気揚々とPDOでSELECTを実行したのですが、また文字化け。失敗

② SET NAMES

SET NAMESは色々と問題があるらしく、非推奨の方法。

その点については詳しくは触れないですが、試してみました。

$dbh = new PDO($dsn, $user, $password);
$dbh->query('SET NAMES utf8');

これで試すと、なんと成功

でも非推奨だもんなぁ…。他の方法も引き続き調査。

③ mysql_set_charset

SET NAMESがダメでも、こっちはOK的な感じでどこかに書いてありました。

試してみました。

$dbh = new PDO($dsn, $user, $password);
// $dbh->query('SET NAMES utf8');
mysql_set_charset('utf8');

これでSELECTを実行すると、また?????に文字化けしてしまいました。失敗

④ MYSQL_ATTR_INIT_COMMAND

諦めかけていた所で、やっとこ解決策にたどり着きました。

PHP, PDOでMySQL接続時の文字コード設定 | beginsprite log

PDO::MYSQL_ATTR_INIT_COMMAND => “SET CHARACTER SET `utf8`” 。。。!!??

さっそく挑戦です。

$dbh = new PDO($dsn, $user, $password,
    array(
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET `utf8`"
    )
);

これで再度ためしてみると、大成功!!

良かった…と、思ったのですが、、、

http://dev.mysql.com/doc/refman/4.1/ja/charset-connection.html

 SET CHARACTER SET は、SET NAMES を含んでいるほか、・・・

あれ、、、これは駄目そうな雰囲気です。。。

⑤ MYSQL_ATTR_READ_DEFAULT_FILE

もうよく分からない。と思っていたら、どうやらPHPのバージョンの問題である疑惑が浮上しました(自分のphpのバージョンは5.3.3)

PHP 5.3.6より前のバージョンの PDO MySQL で charset を指定する

上記を参考に以下のようにしてみました。


$options = array(
PDO::MYSQL_ATTR_READ_DEFAULT_FILE => '/etc/my.cnf',
);
$dbh = new PDO($dsn, $user, $password, $options);


これで試すと。。。今度こそ大成功!!

終わりに

「PDOの中の libmysql に charset を知らせる」というのができていなかったようですね。

よく見返すと、上の方のコードでこの設定やってますね。。。

この辺は間違ってると怖いので、またよく調べなおそうと思います。