Web系のこととかー。

めったにないタイプですが、つい先日すでに存在しているデータベースを使用して新しいサイトを立ち上げるという案件がありました。しかも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;
}

もっとスマートな方法があるのかもしれないですが、とりあえずこれで複数データベースに対応ができます。

§343 · 4月 28, 2011 · CakePHP, プログラミング_PHP · · [Print]

2 Comments to “CakePHPで、MySQLとPostgreSQL両方を使いつつ、さらに違う文字コードに対応する方法”

  1. 通りすがりの名無しさん より:

    PostgreSQLのInternal EncodingとClient Encodingでのコード変換について調べていてたどり着きました。

    Myのコミットが完了し、Pgのコミットが完了する前に電源やプロセスが落ちたりネットワークが切れたりすると、MyとPg不整合が生じますから、それを避けるには分散トランザクションが必要ですよ。MyとPgが完全に独立しているなら問題ないですが。

  2. zaru より:

    コメントありがとうございます。
    今回の案件では、MySQLとPostgreSQLのデータ自体が完全に独立しているものなので問題ないですが、確かに分散トランザクションが必要ですね。
    分散トランザクションについては、やったことがないので、調べてみます。

Leave a Reply