めったにないタイプですが、つい先日すでに存在しているデータベースを使用して新しいサイトを立ち上げるという案件がありました。しかも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のデータ自体が完全に独立しているものなので問題ないですが、確かに分散トランザクションが必要ですね。
分散トランザクションについては、やったことがないので、調べてみます。