古いシステムのリプレイスをする機会があり、その時に困ったかつ誰も記事にしてなかったのでメモ。
太古のOracleDBとPDO
古いシステムのリプレイスをする案件で、DBはそのまま残し、アプリケーションだけ更新するというものがあった。その時のDBは文字コードがsjisのOracle。
しかも一部のテーブル名やカラム名まで日本語。
PDOでcharsetにAL32UTF8
(この辺よくわからない)を指定することで解決したかと思いきやエラーが発生した。
PDOStatement::fetchAll(): column * data was too large for buffer and was truncated to fit it in *
「バッファに対してデータがデカすぎるから削って入れたぜ。」
エラーメッセージでググってみると、原因は文字コード周りのPDOのバグ(仕様?)だった。
PDOがDBからデータを引っ張ってくる際にバッファを用意するが、そのバッファの大きさより大きいデータが来たためエラー(Warning)が発生しているそう。
要するに、もともとsjisのデータ(n byte
)だったものをutf8(1.5n byte
)で持ってきて、PDOはそれをサイズがn byte
のバッファに入れてしまうため、バッファがオーバーフローしてしまいエラーが発生する。
色々ググって見た結果、やはり同様のエラーをいただいている日本人の方々がいて、DBのカラムの型を変えたり、pdoを書き換えたりしてたが、そんなリスキーなことはしたくなかったので、別の回避策を考えてみた。
回避策
単純かつ簡単。
- 発行するSQLを
utf8
からsjis
に変換してからPDOに渡す。 - PDO <-> Oracle は
sjis
でやってしまう。 - 結果は
sjis
からutf8
に変換し、処理を続ける。
文字コードを変換する処理が手順1と手順3に入る分処理が重くなるが仕方ない。
mb_convert_variables()は便利だけど...
mb_convert_variables()は変数の文字コードをまるっと変換してくれる便利な関数。
今回、手順1と手順3で文字コードをmb_convert_variables()
で変換しようとしたが、手順3で期待した結果が得られなかった。
mb_convert_variables()
は連想配列の値は変換してくれても、連想配列のキーまでは変換してくれない。
今回扱うDBのテーブルには、カラム名に日本語を使っているものが多数あり、キーが日本語になるためキーも変換して欲しかったので、結局自前で実装。
やってることはすごく単純でコメントもついてるから読んでみてください。
あと、誰かいい関数名つけてください。
書いたコード 
/**
* @param mixed $arg 変換前変数
* @return mixed 変換後
*/
function convertArraySjisToUtf8($arg) {
if (is_array($arg)) {
foreach ($arg as $sKey => $value) {
// キーをsjisからutf8に変換
$uKey = mb_convert_encoding($sKey, "UTF-8", "SJIS");
// キーがsjisの元要素は消す
unset($arg[$sKey]);
// キーと値をutf8にして入れ直す
$arg[$uKey] = convertArraySjisToUtf8($value); // 再帰
}
} else {
$arg = mb_convert_encoding($arg, "UTF-8", "SJIS");
}
return $arg;
}
これをSQLを発行する前と、結果を取得する前に噛ませることで万事解決!
「もっとこうした方がいいぞ」とかご意見がありましたら是非ください。