めったにないタイプですが、つい先日すでに存在しているデータベースを使用して新しいサイトを立ち上げるという案件がありました。しかもMySQLはUTF-8だけど、PostgreSQLはEUC-JP…。おぉう。というわけで、CakePHPでサクっと両方のデータベースに対応してみました。
モデルの作成
config/database.php
class DATABASE_CONFIG { //MySQL_DB var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'login' => 'id', 'password' => 'pass', 'database' => 'name', 'prefix' => '', ); //PostgreSQL_DB var $pg = array( 'driver' => 'postgres', 'persistent' => false, 'host' => '192.168.0.100', 'login' => 'id', 'password' => 'pass', 'database' => 'name', 'encoding' => 'EUC_JP', 'prefix' => '', 'port' => 5432, ); }
app_model.php
//PostgreSQL用(EUC) class PgAppModel extends Model { function beforeFind($queryData) { // クエリパラメータの配列を、DB文字コードに変換 array_walk_recursive($queryData, array($this, 'encodeToDbEncoding')); return $queryData; } function afterFind($results, $primary = false) { // 取得結果の配列を、内部文字コードに変換 array_walk_recursive($results, array($this, 'decodeFromDbEncoding')); return $results; } function beforeSave($options = array()) { // saveするデータの配列を、DB文字コードに変換 array_walk_recursive($this->data, array($this, 'encodeToDbEncoding')); return true; } function query() { // Model::queryは可変の引数をとるので同じようオーバーライドにする $params = func_get_args(); array_walk_recursive($params, array($this, 'encodeToDbEncoding')); $results = call_user_func_array(array('Model', 'query'), $params); if (is_array($results)) { $results = $this->afterFind($results); } return $results; } function encodeToDbEncoding(&$value, $key) { if (is_string($value)) { $value = mb_convert_encoding($value, 'EUC-JP', Configure::read('App.encoding')); } } function decodeFromDbEncoding(&$value, $key) { if (is_string($value)) { //機種依存文字をEUC-JP -> UTF-8にすると文字化けするので、一旦 sjis-win に変換する //$value = mb_convert_encoding(mb_convert_encoding($value, 'sjis-win','EUC-JP'),Configure::read('App.encoding'), 'sjis-win'); //でも、一部の機種依存のみの対応で大丈夫そうなので、 eucJP-win にする $value = mb_convert_encoding($value,Configure::read('App.encoding'), 'eucJP-win'); } } } //MySQL用(UTF-8) class AppModel extends Model { }
ちょっと気をつけたいのは、EUC-JPからUTF-8に変換する際に、機種依存文字(「①」「㈱」「Ⅰ」など)が文字化けしてしまいます。いったん、sjis-winに変換してからUTF-8に変換をすれば文字化けをすることがありません。よく使われる文字の対応だけであれば、eucJP-winからUTF-8への変換でいけるようです。
各モデル
/** * PostgreSQLを利用するモデル */ class Hoge extends PgAppModel{ var $name = 'Hoge'; var $useDbConfig = 'pg'; //PostgreSQLのデータベース情報に対応 var $useTable = 'hoge'; //CakePHPの命名規則に従ってない場合、ここで指定 var $primaryKey = 'cid'; //プライマリーキーも同様 } /** * MySQLを利用するモデル */ class Fuga extends AppModel{ var $name = 'Fuga'; }
トランザクション
違うデータベースを使用していた場合でも、トランザクションはできます。
//それぞれのモデルをロード App::import('Model','MyModel'); App::import('Model','PgModel'); $my = new MyModel; $pg = new PgModel; //MySQLトランザクション $dataSourceMy = $my->getDataSource(); $dataSourceMy->begin($my); //PostgreSQLトランザクション $dataSourcePg = $pg->getDataSource(); $dataSourcePg->begin($pg); //結果格納用 $return = array(); //登録処理 $return[] = $my->save($data); $return[] = $pg->save($data); //ロールバック・コミット if(in_array(false,$return,true)) { $dataSourceMy->rollback($my); $dataSourcePg->rollback($pg); return false; } else { $dataSourceMy->commit($my); $dataSourcePg->commit($pg); return true; }
もっとスマートな方法があるのかもしれないですが、とりあえずこれで複数データベースに対応ができます。
PostgreSQLのInternal EncodingとClient Encodingでのコード変換について調べていてたどり着きました。
Myのコミットが完了し、Pgのコミットが完了する前に電源やプロセスが落ちたりネットワークが切れたりすると、MyとPg不整合が生じますから、それを避けるには分散トランザクションが必要ですよ。MyとPgが完全に独立しているなら問題ないですが。
コメントありがとうございます。
今回の案件では、MySQLとPostgreSQLのデータ自体が完全に独立しているものなので問題ないですが、確かに分散トランザクションが必要ですね。
分散トランザクションについては、やったことがないので、調べてみます。