Zend_Db_Tableを使った複数DB接続

Zend_Db_Tableを使ったモデルクラス周りは、Zend Frameworkクイックスタート モデルとデータベーステーブルの作成に書かれていたようにTable Data Gatewayパターンを使って書くとして、そういう場合にレプリケーション構成の複数DBを使い分ける(selectを複数のスレーブに分散させる)のはどうやって書けばいいのか考えてみた。

最近はミドルウェアレイヤーでその辺に対応するための情報も増えてきているんで、本格的に対応するならばそういうのを使った方がいいような気もするけど、PHPロジックレイヤーで対応する方法もあってもいいだろう。

実際にコードを動作させてみたわけじゃないんだけど、たぶんこんな感じで動くんじゃないかと思われる擬似コードを書いてみる。

application.ini(の一部)

# マスターDB設定
db.adapter = PDO_MySQL
db.params.host = ****
db.params.username = ****
db.params.password = ****
db.params.dbname = ****
# スレーブDB設定1
slaveDbs.slave1.adapter = PDO_MySQL
slaveDbs.slave1.params.host = ****
slaveDbs.slave1.params.username = ****
slaveDbs.slave1.params.password = ****
slaveDbs.slave1.params.dbname = ****
# スレーブDB設定2
slaveDbs.slave2.adapter = PDO_MySQL
slaveDbs.slave2.params.host = ****
slaveDbs.slave2.params.username = ****
slaveDbs.slave2.params.password = ****
slaveDbs.slave2.params.dbname = ****

Bootstrapクラス(の一部)

class Bootstrap extends Zend_Applicaiton_Bootstrap_Bootstrap
{
    protected function _initDb()
    {
        $options = new Zend_Config($this->getOptions());
        $masterConfig = $options->db;
        $masterDb = Zend_Db::factory($masterConfig->adapter, $masterConfig);
        Zend_Registry::set('db', $masterDb);

        $slaveDbs = array();
        $slaveConfigs = $options->slaveDbs;
        foreach ($slaveConfigs as $configName => slaveConfig) {
            $slaveDbs[$configName] = Zend_Db::factory($slaveConfig->adapter, $slaveConfig);
        }

        Zend_Db_Table::setDefaultAdapter($masterDb);
        Zend_Registry::set('slaveDbConnections', $slaveDbs);

        return $masterDb;
    }
}

Zend_Db_Tableを使ったDBアクセスクラス

class My_DbTable_Foo extends Zend_Db_Table
{
    protected $_name = 'foo';
}

マッパークラス

class My_Mapper_Foo
{
    protected $_masterDbTable;
    protected $_slaveDbTable;

    // バックエンドにマスターDBを使ったend_Db_Tableオブジェクト
    public function getMasterDbTable()
    {
        if (null == $this->_masterDbTable) {
            $this->_masterDbTable = new My_DbTabel_Foo();
        }
        return $this->_masterDbTable;
    }

    // バックエンドにスレーブDBを使ったZend_Db_Tableオブジェクト
    public function getSlaveDbTable()
    {
        if (null == $this->_slaveDbTable) {
            // スレーブDBの中からランダムに一つのコネクションを選択する
            $slaveDbs = Zend_Registry::get('slaveDbConnections');
            $slaveDb = array_rand($slaveDbs);
            $this->_slaveDbTable = new My_DbTable_Foo(array('db' => $slaveDb));
        }
        return $this->_slaveDbTable;
    }

    public function find($fooId, $foo)
    {
        // プライマリーキーからのデータ取得はマスターDBから
        $row = $this->getMasterDbTable()->find($fooId)->current();
        // 中略
        return $foo;
    }

    public function save($foo)
    {
        $fooId = $foo->getFooId();
        $data = array(
            // $dataに$fooの各値をセット
        );
        if (is_null($fooId)) {
            // 更新処理はマスターDBに
            $this->getMasterDbTable()->insert($data);
        } else {
            // 更新処理はマスターDBに
            $this->getMasterDbTable()->update($data, array('foo_id = ?', $fooId);
        }
    }

    public function searchBySomeConditions($someConditions)
    {
        // 複雑な検索処理などはスレーブDBを使う
        $rowset = $this->getSlaveDbTale()->fetchAll($someConditions);
        // $rowsetを汎用形式$resultに変換
        return $result;
    }
}

モデルクラス

class My_Foo
{
    protected $_mapper;

    public funstion getMapper()
    {
       if (null == $this->_mapper) {
           $this->_mapper = new My_Mapper_Foo();
       }
       return $this->_mapper;
    }

    public function save()
    {
        return $this->getMapper()->save($this);
    }

    public function find($fooId)
    {r
        return $this->getMapper()->find($fooId, $this);
    }

    public function searchBySomeConditions($someConditions)
    {
        return $this->getMapper()->searchBySomeConditions($someConditions);
    }
}

標準のリソースと組み合わせて使うならもうちょい練った方がよさそうだ。とか。スレーブを選択するロジックをもうちょい融通を利かせられるようにした方がいいかも。とか、考え始めるといろいろ気になりはじめるんだけど、雰囲気としてはこんな感じの書き方で、Zend_Db_Table自体に手を加えずに、クエリーごとに接続するバックエンドDBを切り替えることができそうだよね。