プログラミングメモ

主にWeb(PHP, JavaScript, etc.)関連のプログラミングメモをしていきます。

DBのMaster-Slave対応

https://github.com/primeminister/cake-master-slave-setup
こちらをCakePHP 2.xに対応させたものです。
ついでに色々いじったのでオリジナルとは使い方が違います。

app/Model/Datasource/Database/MysqlMasterSlave.php

<?php
App::uses('Mysql', 'Model/Datasource/Database');

/**
 * https://github.com/primeminister/cake-master-slave-setup をCake 2.xに対応させたもの。
 */
class MysqlMasterSlave extends Mysql {

	public $description = "MySQL DBO Driver with support for Master/Slave database setup";

/**
 * Constructor.
 *
 * @see DboSource::__construct();
 */
	public function __construct($config = null, $autoConnect = true) {
		parent::__construct($config, $autoConnect);
	}

/**
 * Override execute to use master or slave connection.
 *
 * @see DboSource::_execute()
 */
	protected function _execute($sql, $params = array(), $prepareOptions = array()) {
		$updates = array('CREATE', 'DELETE', 'DROP', 'INSERT', 'UPDATE', 'TRUNCATE', 'REPLACE', 'START TRANSACTION', 'BEGIN', 'COMMIT', 'ROLLBACK');

		$trimmed_sql = trim($sql);

		if (preg_match('/^(SET NAMES)/i', $trimmed_sql)) {
			// not needed to set a connection
			// as 'set names' is invoked in the connection constructor
			// whether 'encoding' is specified on the connection
			// beware though: explicitly setting a connection here
			// results in connection constructor being called again
			// (and again and again ...)
		} else {
			$datasource = preg_match('/^(' . implode('|', $updates) . ')/i', $trimmed_sql) ? 'master' : 'default';
			$this->setConnection($datasource);
			//debug($sql);
			//debug($datasource);
		}

		return parent::_execute($sql, $params, $prepareOptions);
	}

/**
 * Switch the datasource to 'master' when beginning a transaction.
 *
 * @see DboSource::begin()
 */
	public function begin() {
		$this->setConnection('master');

		return parent::begin();
	}

/**
 * Switch the connection based on name.
 * Accepted names are 'master' and 'default' (a slave).
 * If in the middle of a transaction the 'master' connection will always be used.
 */
	protected function setConnection($name = 'default') {
		if ($this->_transactionStarted) {
			$name = 'master';
		}

		$datasource = ConnectionManager::getDataSource($name);
		if (!$datasource->isConnected())	{
			$datasource->connect();
		}

		$this->_connection = $datasource->getConnection();
	}

}

app/Config/database.php

class DATABASE_CONFIG {

	public $default;
	public $master;

	private static $selectedSlaveIndex;

	public function __construct() {
		$default = array(
			'datasource' => 'Database/MysqlMasterSlave',
			'persistent' => false,
			'prefix' => '',
			'encoding' => 'utf8'
		);

		$this->master = array_merge($default, Configure::read('Database.master'));

		$slaves = array();
		foreach (Configure::read('Database.slaves') as $slave) {
			$slaves[] = array_merge($default, $slave);
		}

		if (count($slaves)) {
			if (!isset(self::$selectedSlaveIndex)) {
				self::$selectedSlaveIndex = rand(0, count($slaves) - 1);
			}
			$this->default = $slaves[self::$selectedSlaveIndex];
		} else {
			$this->default = $this->master;
		}
	}

}

app/Config/core.php
配列の内容は通常のdatabase.phpに記述するものと同じです。

Configure::write('Database', array(
	'master' => array(
		'host' => 'Masterホスト名',
		//'port' => ポート番号,
		'database' => 'DB名',
		'login' => 'ログイン名',
		'password' => 'パスワード'
	),
	'slaves' => array(
		array(
			'host' => 'Slaveホスト名1',
			'port' => ポート番号,
			'database' => 'DB名',
			'login' => 'ログイン名',
			'password' => 'パスワード'
		),
		array(
			'host' => 'Slaveホスト名2', ...
		)
	)
));

あと、トランザクション

$this->MODEL->getDataSource()->begin();

のようにするか、
app/Model/AppModel.php

	public function begin() {
		$this->getDataSource()->begin();
	}

	public function commit() {
		$this->getDataSource()->commit();
	}

	public function rollback() {
		$this->getDataSource()->rollback();
	}

のようにしておく。