DoctrineのMaster&Slaveのコネクションを操作するクラスを作成する方法
こんにちは。笹亀です。
symfonyはバージョン2かはSymfonyと頭文字が大文字表記となるとのことで、1.0のころに間違えてSymfonyと書いてツッコミを入れられたことを思い出しました。
さて本日はDoctrineのコネクションをMaster(更新 INSERT,UPDATE,DELETE)とSlave(選択 SELECT)で切り替えを行うProjectConfigurationとDoctrine_Connectionを継承したコネクションを操作するクラスを作成する方法について、ご紹介していきたいと思います。
ある程度の規模の開発をするときにどうしても必要になり、PropelにはあるのになぜDoctrineにはないのだと思い、いろいろソースとWebページを参考に調べながら作成しました。
config/ProjectConfiguration.class.php
Doctrine_EventListener_Interfaceのimplementsとして作成します。preQuery,prePrepareをフックして使用するコネクションを変更します。トランザクション処理をしているときは必ずMaster側のコネクションを使用することを気をつけながら実装が必要です。
lib/ConnectionListener.class.php
データベースの接続情報を作成、変更します。databases.ymlだけではどうしてもうまく実装ができなかったので、Masterはdatabases.ymlに記載して、slaveはdatabase_slaves.ymlを作成して設置することにしました。
databases.yml
database_slaves.yml
作成当時はまだDocrtineのMaster&Slave構成のConnection操作に対応をしておりませんでしたが、2010年2月24日にプラグインとして正式にリリースされました。
http://www.symfony-project.org/plugins/sfDoctrineMasterSlavePlugin
自分が作成するときに参考にさせていただいたsymfonyのプロジェクトチームのKris Wallsmith氏がリリースしたプラグインですので、信頼してご利用いただけるのではないでしょうか。
※この前、試しに使用してみたらうまく動かすことができませんでした;;
動かしたことがある方、是非とも教えていただけますと幸いです。
symfonyはバージョン2かはSymfonyと頭文字が大文字表記となるとのことで、1.0のころに間違えてSymfonyと書いてツッコミを入れられたことを思い出しました。
さて本日はDoctrineのコネクションをMaster(更新 INSERT,UPDATE,DELETE)とSlave(選択 SELECT)で切り替えを行うProjectConfigurationとDoctrine_Connectionを継承したコネクションを操作するクラスを作成する方法について、ご紹介していきたいと思います。
ある程度の規模の開発をするときにどうしても必要になり、PropelにはあるのになぜDoctrineにはないのだと思い、いろいろソースとWebページを参考に調べながら作成しました。
config/ProjectConfiguration.class.php
- require_once
dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php'; - sfCoreAutoload::register();
- class
ProjectConfiguration extends sfProjectConfiguration - {
protected $masterConnection = null, $slaveConnection = null; public function initializeConnections() { //Databaseへの接続情報を取得してコネクションのセット(Slave情報のみを取得) $file = sfConfig::get('sf_config_dir').'/database_slaves.yml'; $config = file_exists($file) ? sfYaml::load($file) : array(); $slave_connections = array(); foreach ($config['all'] as $name => $connection) { switch ($name) { case 'master': break; default: $dsn['slave'][] = $connection['param']['dsn']; break; } } //Slaveの振り分け処理(とりあえずはランダム選択 $slave_num = rand(0,count($dsn['slave']) - 1); Doctrine_Manager::connection($dsn['slave'][$slave_num], 'slave'); //Masterを必ずCurrentConnectionとしておく Doctrine_Manager::getInstance()->setCurrentConnection('master'); //Slaveとマスタのコネクションのデフォルトセット $slaves = array(); foreach (Doctrine_Manager::getInstance()->getConnections() as $name => $conn) { switch (true) { case 'master' == $name: $this->masterConnection = $conn; break; case 0 === strpos($name, 'slave'): $slaves[] = $conn; break; } } if (is_null($this->masterConnection)) { $this->masterConnection = Doctrine_Manager::connection(); } } //Masterのコネクションを取得する public function getMasterConnection() { $this->masterConnection || $this->initializeConnections(); return $this->masterConnection; } //Slaveのコネクションを取得する public function getSlaveConnection() { $this->slaveConnection || $this->initializeConnections(); return $this->slaveConnection; } public function configureDoctrineConnection(Doctrine_Connection $conn) { $listener = new ConnectionListener( $this->getMasterConnection()->getDbh(), $this->getSlaveConnection()->getDbh() ); $conn->addListener($listener); } public function setup() { $this->enablePlugins('sfDoctrinePlugin'); } public function configureDoctrine($manager) { $manager->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, true); } - }
Doctrine_EventListener_Interfaceのimplementsとして作成します。preQuery,prePrepareをフックして使用するコネクションを変更します。トランザクション処理をしているときは必ずMaster側のコネクションを使用することを気をつけながら実装が必要です。
lib/ConnectionListener.class.php
- class
ConnectionListener extends Doctrine_Connection implements Doctrine_EventListener_Interface - {
protected $master = null, $slave = null; public function __construct(PDO $master, PDO $slave) { $this->master = $master; $this->slave = $slave; } public function preQuery(Doctrine_Event $event) { //Transaction時は必ずmasterにコネクションをはるようにする //コネクション情報にmodulesにトランザクションがつかわれているPraivate変数がある //$conn->transaction->getState(); 0 = sleep ,1 = active, 2 = busy $conn = $event->getInvoker(); if ($conn->transaction->getState() == 0) { $this->forceDbh($conn, 'slave'); } else { $this->forceDbh($conn, 'master'); } } public function postQuery(Doctrine_Event $event) { $this->restoreDbh($event->getInvoker()); } public function prePrepare(Doctrine_Event $event) { //コネクション情報を取得 //トランザクション中かチェック() //$conn->transaction->getState(); 0 = sleep ,1 = active, 2 = busy $conn = $event->getInvoker(); if ($conn->transaction->getState() == 0) { $use = 0 === strpos(trim(strtolower($event->getQuery())), 'select') ? 'slave' : 'master'; $this->forceDbh($conn, $use); } else { $this->forceDbh($conn, 'master'); } } public function postStmtExecute(Doctrine_Event $event) { $this->restoreDbh($event->getInvoker()->getConnection()); } public function preExec(Doctrine_Event $event) { $this->forceDbh($event->getInvoker(), 'master'); } public function postExec(Doctrine_Event $event) { $this->restoreDbh($event->getInvoker()); } // protected protected function forceDbh($conn, $type) { if ($this->$type !== $conn->dbh) { $conn->options['previous_dbh'] = $conn->dbh; $conn->dbh = $this->$type; } } protected function restoreDbh($conn) { if (isset($conn->options['previous_dbh'])) { $conn->dbh = $conn->options['previous_dbh']; unset($conn->options['previous_dbh']); } } // // the remaining methods required by Doctrine_EventListener_Interface public function preTransactionCommit(Doctrine_Event $event) { } public function postTransactionCommit(Doctrine_Event $event) { } public function preTransactionRollback(Doctrine_Event $event) { } public function postTransactionRollback(Doctrine_Event $event) { } public function preTransactionBegin(Doctrine_Event $event) { } public function postTransactionBegin(Doctrine_Event $event) { } public function postConnect(Doctrine_Event $event) { } public function preConnect(Doctrine_Event $event) { } public function postPrepare(Doctrine_Event $event) { } public function preStmtExecute(Doctrine_Event $event) { } public function preError(Doctrine_Event $event) { } public function postError(Doctrine_Event $event) { } public function preFetch(Doctrine_Event $event) { } public function postFetch(Doctrine_Event $event) { } public function preFetchAll(Doctrine_Event $event) { } public function postFetchAll(Doctrine_Event $event) { } - }
データベースの接続情報を作成、変更します。databases.ymlだけではどうしてもうまく実装ができなかったので、Masterはdatabases.ymlに記載して、slaveはdatabase_slaves.ymlを作成して設置することにしました。
databases.yml
- all:
master: class: sfDoctrineDatabase param: dsn: mysql:host=localhost;dbname=test username: user password: hogehoge
database_slaves.yml
- all:
slave_1: class: sfDoctrineDatabase param: dsn: mysql://user:hogehoge@localhost/test slave_2: class: sfDoctrineDatabase param: dsn: mysql://user:hogehoge@localhost/test
作成当時はまだDocrtineのMaster&Slave構成のConnection操作に対応をしておりませんでしたが、2010年2月24日にプラグインとして正式にリリースされました。
http://www.symfony-project.org/plugins/sfDoctrineMasterSlavePlugin
自分が作成するときに参考にさせていただいたsymfonyのプロジェクトチームのKris Wallsmith氏がリリースしたプラグインですので、信頼してご利用いただけるのではないでしょうか。
※この前、試しに使用してみたらうまく動かすことができませんでした;;
動かしたことがある方、是非とも教えていただけますと幸いです。
コメントフォーム
トラックバック
最近の記事
- もうすぐ健康診断があるんだ・・・ [2010年09月02日 : 阿部恵]
- Photoshopで壁紙を作りながら、基本的な使い方を覚える [2010年09月01日 : 鴨田健次]
- はじめての共同作業 Canvas編 (node.js + websocket) [2010年09月01日 : 中川善樹]
- 「PHP×Flex(後編)」PHPテクニカルセミナー(無料)第4弾の募集を開始しました!! [2010年08月26日 : 和田記光]
- 【HTML5】Canvasでお絵かきしてみた(前編) [2010年08月25日 : 橋本章史]
- MacにgroongaのMySQL用ストレージエンジン [2010年08月23日 : 笹亀弘]
- Appleのサイトで見たiPhone4をFireworksで描いてみました-1/2 [2010年08月19日 : 和田記光]
- iPad版の会社紹介を作ってみました [2010年08月19日 : 小林有佳]
- iPhoneアプリ開発開始時に気をつけるべきファイルの取り扱い (2) [2010年08月19日 : 亀本大地]
- symfonyセミナー動画無料公開! [2010年08月13日 : 岡本雄樹]



最近のコメント