Author: Kris.Wallsmith
Date: 2010-02-20 02:11:48 +0100 (Sat, 20 Feb 2010)
New Revision: 28144
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/
plugins/sfDoctrineMasterSlavePlugin/trunk/LICENSE
plugins/sfDoctrineMasterSlavePlugin/trunk/README
plugins/sfDoctrineMasterSlavePlugin/trunk/config/
plugins/sfDoctrineMasterSlavePlugin/trunk/config/sfDoctrineMasterSlavePluginConfiguration.class.php
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/collection/
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/collection/sfDoctrineMasterSlaveCollection.class.php
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveConnectionManager.class.php
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveDebugListener.class.php
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/query/
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/query/sfDoctrineMasterSlaveQuery.class.php
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/record/
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/record/sfDoctrineMasterSlaveRecord.class.php
plugins/sfDoctrineMasterSlavePlugin/trunk/package.xml.tmpl
plugins/sfDoctrineMasterSlavePlugin/trunk/test/
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveConnectionManagerTest.php
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveDebugListenerTest.php
Log:
[sfDoctrineMasterSlavePlugin] initial import
Added: plugins/sfDoctrineMasterSlavePlugin/trunk/LICENSE
===================================================================
--- plugins/sfDoctrineMasterSlavePlugin/trunk/LICENSE
(rev 0)
+++ plugins/sfDoctrineMasterSlavePlugin/trunk/LICENSE 2010-02-20 01:11:48 UTC
(rev 28144)
@@ -0,0 +1,7 @@
+Copyright (c) Kris Wallsmith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Added: plugins/sfDoctrineMasterSlavePlugin/trunk/README
===================================================================
--- plugins/sfDoctrineMasterSlavePlugin/trunk/README
(rev 0)
+++ plugins/sfDoctrineMasterSlavePlugin/trunk/README 2010-02-20 01:11:48 UTC
(rev 28144)
@@ -0,0 +1,153 @@
+The `sfDoctrineMasterSlavePlugin` plugin manages database connections and
+directs queries to the appropriate connection: either the master or a slave
+database.
+
+Once the plugin is enabled you can mark a connection as the master connection
+in `databases.yml` by using the string "master" in its name:
+
+ all:
+ master:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=database;host:master.example.com
+ username: root
+ password: ~
+ slave:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=database;host:slave.example.com
+ username: root
+ password: ~
+
+Alternatively, you can provide an `is_master` parameter in the configuration:
+
+ all:
+ doctrine:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=database;host:master.example.com
+ username: root
+ password: ~
+ is_master: true
+
+If no connection is marked as the master connection using either of these
+techniques, the first connection configured first will be used. If multiple
+connections are marked as master, only the last connection marked will be used
+as the master connection; the others will be used as slaves.
+
+If your configuration includes more than one slave database, the plugin will
+select one of them at random to use for the duration of the request.
+
+Accessing the right connection
+------------------------------
+
+You can access the master or slave connections from the configuration object:
+
+ [php]
+ ProjectConfiguration::getActive()->getMasterConnection();
+ ProjectConfiguration::getActive()->getSlaveConnection();
+
+The plugin interacts with Doctrine as your database connection objects are
+created and updates the current connection set in the `Doctrine_Manager` to
+be the master connection. This way, any calls to
+`Doctrine_Manager::connection()` will return the master connection, which is
+usually what you need when calling that method (i.e. when beginning a
+transaction).
+
+>**NOTE**
+>The slave database accessor `->getSlaveConnection()` includes logic to check
+>whether the master database has any open transactions, and will return the
+>master connection in that case.
+
+Choosing from multiple slaves
+-----------------------------
+
+If you have multiple slave connections configured in `databases.yml`, the
+plugin will choose one at random to use for the duration of the current
+request. If you want to customize the logic surrounding how a slave connection
+is selected, you can do so by listening to the `doctrine.select_slave` event.
+
+ /** Listens to the doctrine.select_slave event. */
+ public function selectSlave(sfEvent $event)
+ {
+ if (in_array('slave1', $event['slaves]))
+ {
+ $event->setReturnValue('slave1');
+ return true;
+ }
+ }
+
+The `doctrine.select_slave` event includes the following parameters:
+
+ * `group`: The group to select a slave connection from
+ * `master`: The name of the master connection
+ * `slaves`: An array of slave connection names
+
+Notice this event receives the names of connections as parameters, not the
+connection objects themselves. You can access the connection objects in your
+event listener by calling
+`Doctrine_Manager::getInstance()->getConnection($name)`.
+
+Connecting to multiple schemas
+------------------------------
+
+If your schema assigns different connections to different models, you will
+need to organize the master and slave connections for each of these schema
+into groups using the `group` parameter:
+
+ all:
+ # db1 connections
+ db1_master:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=db1;host:db1-master.example.com
+ username: root
+ password: ~
+ group: db1
+ db1_slave:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=db1;host:db1-slave.example.com
+ username: root
+ password: ~
+ group: db1
+
+ # db2 connections
+ db2_master:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=db2;host:db2-master.example.com
+ username: root
+ password: ~
+ group: db2
+ db2_slave:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=db2;host:db2-slave.example.com
+ username: root
+ password: ~
+ group: db2
+
+Emulating read-only connections
+-------------------------------
+
+It's important to be able to test whether your connection management is
+working correctly even when you're not connecting to any read-only databases.
+The plugin provides a connection listener that emulates a read-only connection
+for this purpose. Setup distinct master and slave connections in
+`databases.yml` to enable this emulation in debug and test modes.
+
+ dev:
+ doctrine:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=database;host=localhost
+ username: root
+ password: ~
+ is_master: true
+ slave:
+ class: sfDoctrineDatabase
+ param:
+ dsn: mysql:dbname=database;host=localhost
+ username: root
+ password: ~
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/config/sfDoctrineMasterSlavePluginConfiguration.class.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/config/sfDoctrineMasterSlavePluginConfiguration.class.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/config/sfDoctrineMasterSlavePluginConfiguration.class.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * Plugin configuration.
+ *
+ * Adds the following parameters to each Doctrine database:
+ *
+ * * is_master: Whether the database is a master connection
+ * * group: Groups a connection with others
+ *
+ * @package sfDoctrineMasterSlavePlugin
+ * @subpackage config
+ * @author Kris Wallsmith <[email protected]>
+ * @version SVN: $Id$
+ */
+class sfDoctrineMasterSlavePluginConfiguration extends sfPluginConfiguration
+{
+ protected
+ $connectionManager = null;
+
+ /**
+ * @see sfPluginConfiguration
+ */
+ public function configure()
+ {
+ $a = array_search('sfDoctrineMasterSlavePlugin',
$this->configuration->getPlugins());
+ $b = array_search('sfDoctrinePlugin', $this->configuration->getPlugins());
+
+ if ($a > $b)
+ {
+ throw new LogicException('The sfDoctrineMasterSlavePlugin plugin must be
enabled before sfDoctrinePlugin');
+ }
+ }
+
+ /**
+ * @see sfPluginConfiguration
+ */
+ public function initialize()
+ {
+ $this->connectionManager = new
sfDoctrineMasterSlaveConnectionManager($this->dispatcher);
+
+ $this->dispatcher->connect('configuration.method_not_found', array($this,
'listenForConfigurationMethodNotFound'));
+ $this->dispatcher->connect('doctrine.configure', array($this,
'configureDoctrine'));
+ $this->dispatcher->connect('doctrine.configure_connection', array($this,
'configureDoctrineConnection'));
+ $this->dispatcher->connect('doctrine.filter_model_builder_options',
array($this, 'filterBuilderOptions'));
+ }
+
+ /**
+ * Returns the current connection manager.
+ *
+ * @return sfDoctrineMasterSlaveConnectionManager
+ */
+ public function getConnectionManager()
+ {
+ return $this->connectionManager;
+ }
+
+ /**
+ * Listens for the configuration.method_not_found event.
+ *
+ * Adds accessors for master and slave connections to the configuration
object.
+ *
+ * @param sfEvent $event A symfony event
+ *
+ * @return boolean Returns true if the event was processed
+ */
+ public function listenForConfigurationMethodNotFound(sfEvent $event)
+ {
+ switch ($event['method'])
+ {
+ case 'getMasterConnection':
+ case 'getSlaveConnection':
+
$event->setReturnValue(call_user_func_array(array($this->connectionManager,
$event['method']), $event['arguments']));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Configures Doctrine.
+ *
+ * Adds custom query and collection classes if none are setup already.
+ *
+ * @param sfEvent $event A symfony event
+ */
+ public function configureDoctrine(sfEvent $event)
+ {
+ $manager = $event->getSubject();
+
+ if ('Doctrine_Query' ==
$manager->getAttribute(Doctrine_Core::ATTR_QUERY_CLASS))
+ {
+ $manager->setAttribute(Doctrine_Core::ATTR_QUERY_CLASS,
'sfDoctrineMasterSlaveQuery');
+ }
+
+ if ('Doctrine_Collection' ==
$manager->getAttribute(Doctrine_Core::ATTR_COLLECTION_CLASS))
+ {
+ $manager->setAttribute(Doctrine_Core::ATTR_COLLECTION_CLASS,
'sfDoctrineMasterSlaveCollection');
+ }
+ }
+
+ /**
+ * Configures a Doctrine connection.
+ *
+ * Registers each connection with the current master/slave connection
manager.
+ *
+ * @param sfEvent $event A symfony event
+ */
+ public function configureDoctrineConnection(sfEvent $event)
+ {
+ $database = $event['database'];
+ $conn = $event['connection'];
+
+ $this->connectionManager->register($conn,
$database->getParameter('group'), $database->getParameter('is_master'));
+
+ if (sfConfig::get('sf_debug') || sfConfig::get('sf_test'))
+ {
+ $callable = new sfCallable(array($this->connectionManager,
'getMasterConnection'));
+ $conn->addListener(new sfDoctrineMasterSlaveDebugListener($callable),
'slave_emulator');
+ }
+ }
+
+ /**
+ * Filters Doctrine builder options.
+ *
+ * @param sfEvent $event A symfony event
+ * @param array $options An array of builder options
+ *
+ * @return array The filtered array of builder options
+ */
+ public function filterBuilderOptions(sfEvent $event, $options)
+ {
+ if ('sfDoctrineRecord' == $options['baseClassName'])
+ {
+ $options['baseClassName'] = 'sfDoctrineMasterSlaveRecord';
+ }
+ }
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/config/sfDoctrineMasterSlavePluginConfiguration.class.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/collection/sfDoctrineMasterSlaveCollection.class.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/collection/sfDoctrineMasterSlaveCollection.class.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/collection/sfDoctrineMasterSlaveCollection.class.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Overrides certain methods that require a master connection.
+ *
+ * @package sfDoctrineMasterSlavePlugin
+ * @subpackage collection
+ * @author Kris Wallsmith <[email protected]>
+ * @version SVN: $Id$
+ */
+class sfDoctrineMasterSlaveCollection extends Doctrine_Collection
+{
+ /**
+ * Saves the current collection of records.
+ *
+ * Forces a master connection.
+ *
+ * @see Doctrine_Collection
+ */
+ public function save(Doctrine_Connection $conn = null, $processDiff = true)
+ {
+ $conn = ProjectConfiguration::getActive()->getMasterConnection($conn ?
$conn : $this->getTable()->getConnection());
+
+ return parent::save($conn, $processDiff);
+ }
+
+ /**
+ * Deletes the current collection of records.
+ *
+ * Forces a master connection.
+ *
+ * @see Doctrine_Collection
+ */
+ public function delete(Doctrine_Connection $conn = null, $clearColl = true)
+ {
+ $conn = ProjectConfiguration::getActive()->getMasterConnection($conn ?
$conn : $this->getTable()->getConnection());
+
+ return parent::delete($conn, $clearColl);
+ }
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/collection/sfDoctrineMasterSlaveCollection.class.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveConnectionManager.class.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveConnectionManager.class.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveConnectionManager.class.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * Manages Doctrine master and slave connections.
+ *
+ * @package sfDoctrineMasterSlavePlugin
+ * @subpackage connection
+ * @author Kris Wallsmith <[email protected]>
+ * @version SVN: $Id$
+ */
+class sfDoctrineMasterSlaveConnectionManager
+{
+ const
+ DEFAULT_GROUP = 'default';
+
+ protected
+ $dispatcher = null,
+ $connections = array();
+
+ /**
+ * Constructor.
+ *
+ * @param sfEventDispatcher $dispatcher The event dispatcher
+ */
+ public function __construct(sfEventDispatcher $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * Registers a connection with the current manager.
+ *
+ * @param Doctrine_Connection $conn A Doctrine connection object
+ * @param string $group A connection group name
+ * @param boolean $isMaster Whether this connection is a master
+ */
+ public function register(Doctrine_Connection $conn, $group = null, $isMaster
= null)
+ {
+ if (null === $group)
+ {
+ $group = $this->getDefaultGroup();
+ }
+
+ if (!isset($this->connections[$group]))
+ {
+ // initialize group
+ $this->connections[$group] = array(
+ 'master' => null,
+ 'slaves' => array(),
+ );
+ }
+
+ if (null === $isMaster)
+ {
+ // assume this is the master if another hasn't been setup yet or if the
name includes "master"
+ $isMaster = !isset($this->connections[$group]['master']) || false !==
strpos($conn->getName(), 'master');
+ }
+
+ if ($isMaster)
+ {
+ // don't unregister any assumed master connections
+ if (isset($this->connections[$group]['master']))
+ {
+ $this->connections[$group]['slaves'][] =
$this->connections[$group]['master'];
+ }
+
+ $this->connections[$group]['master'] = $conn->getName();
+ }
+ else
+ {
+ $this->connections[$group]['slaves'][] = $conn->getName();
+ }
+
+ $this->resetCurrentConnection();
+ }
+
+ /**
+ * Returns the master connection for a certain group.
+ *
+ * @param string|Doctrine_Connection $group A Doctrine connection or
connection group name
+ *
+ * @return Doctrine_Connection A Doctrine connection object
+ *
+ * @throws InvalidArgumentException If the group doesn't exists or doesn't
have a master connection
+ */
+ public function getMasterConnection($group = null)
+ {
+ if (null === $group)
+ {
+ $group = $this->getDefaultGroup();
+ }
+
+ if ($group instanceof Doctrine_Connection)
+ {
+ $group = $this->getConnectionGroup($group);
+ }
+
+ if (!isset($this->connections[$group]['master']))
+ {
+ throw new InvalidArgumentException(sprintf('There is no master
connection for the "%s" group', $group));
+ }
+
+ return
$this->getDoctrineManager()->getConnection($this->connections[$group]['master']);
+ }
+
+ /**
+ * Returns a slave connection for a certain group.
+ *
+ * @param string $group A connection group name
+ *
+ * @return Doctrine_Connection A Doctrine connection object
+ *
+ * @throws InvalidArgumentException If the group doesn't exists or doesn't
have a slave connection
+ */
+ public function getSlaveConnection($group = null)
+ {
+ if (null === $group)
+ {
+ $group = $this->getDefaultGroup();
+ }
+
+ // use the master connection if we're in a transaction
+ $master = $this->getMasterConnection($group);
+ if ($master->getTransactionLevel())
+ {
+ return $master;
+ }
+
+ if (!isset($this->connections[$group]['current_slave']))
+ {
+ // select one slave
+ $event = $this->dispatcher->notifyUntil(new sfEvent($this,
'doctrine.select_slave', array(
+ 'group' => $group,
+ 'master' => $this->connections[$group]['master'],
+ 'slaves' => $this->connections[$group]['slaves'],
+ )));
+
+ if ($event->isProcessed())
+ {
+ $this->connections[$group]['current_slave'] = $event->getReturnValue();
+ }
+ else if ($slaves = $this->connections[$group]['slaves'])
+ {
+ $this->connections[$group]['current_slave'] =
$slaves[array_rand($slaves)];
+ }
+ else
+ {
+ $this->connections[$group]['current_slave'] = $master->getName();
+ }
+ }
+
+ return
$this->getDoctrineManager()->getConnection($this->connections[$group]['current_slave']);
+ }
+
+ // protected
+
+ /**
+ * Returns the name of the current default group.
+ *
+ * @return string A connection group name
+ */
+ protected function getDefaultGroup()
+ {
+ if (isset($this->connections[self::DEFAULT_GROUP]) || !$this->connections)
+ {
+ return self::DEFAULT_GROUP;
+ }
+ else
+ {
+ return key($this->connections);
+ }
+ }
+
+ /**
+ * Resets Doctrine's current connection to the default group's master
connection.
+ */
+ protected function resetCurrentConnection()
+ {
+ if (isset($this->connections[$this->getDefaultGroup()]['master']))
+ {
+
$this->getDoctrineManager()->setCurrentConnection($this->connections[$this->getDefaultGroup()]['master']);
+ }
+ }
+
+ /**
+ * Returns a group name based on connection name.
+ *
+ * @param Doctrine_Connection $conn A Doctrine connection
+ *
+ * @return string A connection group name
+ *
+ * @throws InvalidArgumentException If the group name could not be determined
+ */
+ protected function getConnectionGroup(Doctrine_Connection $conn)
+ {
+ $name = $conn->getName();
+
+ foreach ($this->connections as $group => $connections)
+ {
+ if (
+ (isset($connections['master']) && $name == $connections['master'])
+ ||
+ (isset($connections['slaves']) && in_array($name,
$connections['slaves']))
+ )
+ {
+ return $group;
+ }
+ }
+
+ throw new InvalidArgumentException(sprintf('Could not determine a group
for the "%s" connection'));
+ }
+
+ /**
+ * A convenience method for returning the current Doctrine manager.
+ *
+ * @return Doctrine_Manager The current Doctrine manager
+ */
+ protected function getDoctrineManager()
+ {
+ return Doctrine_Manager::getInstance();
+ }
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveConnectionManager.class.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveDebugListener.class.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveDebugListener.class.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveDebugListener.class.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Emulates a read-only database connection by throwing exceptions.
+ *
+ * @package sfDoctrineMasterSlavePlugin
+ * @subpackage connection
+ * @author Kris Wallsmith <[email protected]>
+ * @version SVN: $Id$
+ */
+class sfDoctrineMasterSlaveDebugListener extends Doctrine_EventListener
+{
+ protected
+ $masterConnection = null;
+
+ /**
+ * Constructor.
+ *
+ * @param Doctrine_Connection|sfCallable $masterConnection The master
connection or an sfCallable that returns the master connection
+ *
+ * @throws InvalidArgumentException If the argument is neither a connection
nor sfCallable
+ */
+ public function __construct($masterConnection)
+ {
+ if (!$masterConnection instanceof Doctrine_Connection &&
!$masterConnection instanceof sfCallable)
+ {
+ throw new InvalidArgumentException('Argument must be either a connection
object or sfCallable that returns a connection object.');
+ }
+
+ $this->masterConnection = $masterConnection;
+ }
+
+ /**
+ * Checks that the supplied connection is the master connection.
+ *
+ * @param Doctrine_Connection $conn A connection to check
+ *
+ * @throws LogicException If the connection is not the master connection
+ */
+ public function checkConnection(Doctrine_Connection $conn, $query = null)
+ {
+ if ($this->masterConnection instanceof sfCallable)
+ {
+ $this->masterConnection = $this->masterConnection->call();
+ }
+
+ if ($this->masterConnection !== $conn)
+ {
+ throw new LogicException('Cannot run this query on a read-only
connection: '.$query);
+ }
+ }
+
+ public function preExec(Doctrine_Event $event)
+ {
+ if (0 !== stripos(trim($event->getQuery()), 'set'))
+ {
+ $this->checkConnection($event->getInvoker(), $event->getQuery());
+ }
+ }
+
+ public function prePrepare(Doctrine_Event $event)
+ {
+ if (!preg_match('/^(select|set)/i', trim($event->getQuery())))
+ {
+ $this->checkConnection($event->getInvoker(), $event->getQuery());
+ }
+ }
+
+ public function preTransactionBegin(Doctrine_Event $event)
+ {
+ $this->checkConnection($event->getInvoker()->getConnection(), 'BEGIN
TRANSACTION');
+ }
+
+ public function preTransactionCommit(Doctrine_Event $event)
+ {
+ $this->checkConnection($event->getInvoker()->getConnection(), 'COMMIT');
+ }
+
+ public function preTransactionRollback(Doctrine_Event $event)
+ {
+ $this->checkConnection($event->getInvoker()->getConnection(), 'ROLLBACK');
+ }
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/connection/sfDoctrineMasterSlaveDebugListener.class.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/query/sfDoctrineMasterSlaveQuery.class.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/query/sfDoctrineMasterSlaveQuery.class.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/query/sfDoctrineMasterSlaveQuery.class.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Smartly uses either the master of slaves database connection.
+ *
+ * @package sfDoctrineMasterSlavePlugin
+ * @subpackage query
+ * @author Kris Wallsmith <[email protected]>
+ * @version SVN: $Id$
+ */
+class sfDoctrineMasterSlaveQuery extends Doctrine_Query
+{
+ /**
+ * Pre-query hook.
+ *
+ * Sets the current query's connection based on what type of query is being
run.
+ *
+ * @see Doctrine_Query_Abstract
+ */
+ public function preQuery()
+ {
+ $method = Doctrine_Query::SELECT == $this->getType() ?
'getSlaveConnection' : 'getMasterConnection';
+
$this->setConnection(ProjectConfiguration::getActive()->$method($this->getConnection()));
+ }
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/query/sfDoctrineMasterSlaveQuery.class.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/record/sfDoctrineMasterSlaveRecord.class.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/record/sfDoctrineMasterSlaveRecord.class.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/record/sfDoctrineMasterSlaveRecord.class.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Overrides certain methods that require a master connection.
+ *
+ * @package sfDoctrineMasterSlavePlugin
+ * @subpackage record
+ * @author Kris Wallsmith <[email protected]>
+ * @version SVN: $Id$
+ */
+abstract class sfDoctrineMasterSlaveRecord extends sfDoctrineRecord
+{
+ /**
+ * Saves the current record.
+ *
+ * Forces a master connection.
+ *
+ * @see Doctrine_Record
+ */
+ public function save(Doctrine_Connection $conn = null)
+ {
+ $conn = ProjectConfiguration::getActive()->getMasterConnection($conn ?
$conn : $this->getTable()->getConnection());
+
+ return parent::save($conn);
+ }
+
+ /**
+ * Replaces the current record.
+ *
+ * Forces a master connection.
+ *
+ * @see Doctrine_Record
+ */
+ public function replace(Doctrine_Connection $conn = null)
+ {
+ $conn = ProjectConfiguration::getActive()->getMasterConnection($conn ?
$conn : $this->getTable()->getConnection());
+
+ return parent::replace($conn);
+ }
+
+ /**
+ * Deletes the current record from the database.
+ *
+ * Forces a master connection.
+ *
+ * @see Doctrine_Record
+ */
+ public function delete(Doctrine_Connection $conn = null)
+ {
+ $conn = ProjectConfiguration::getActive()->getMasterConnection($conn ?
$conn : $this->getTable()->getConnection());
+
+ return parent::delete($conn);
+ }
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/lib/record/sfDoctrineMasterSlaveRecord.class.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added: plugins/sfDoctrineMasterSlavePlugin/trunk/package.xml.tmpl
===================================================================
--- plugins/sfDoctrineMasterSlavePlugin/trunk/package.xml.tmpl
(rev 0)
+++ plugins/sfDoctrineMasterSlavePlugin/trunk/package.xml.tmpl 2010-02-20
01:11:48 UTC (rev 28144)
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="##ENCODING##"?>
+<package xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.4.1"
version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
+ <name>sfDoctrineMasterSlavePlugin</name>
+ <channel>plugins.symfony-project.org</channel>
+ <summary>Easily manage master and slave database connections.</summary>
+ <description>Easily manage master and slave database
connections.</description>
+ <lead>
+ <name>Kris Wallsmith</name>
+ <user>Kris.Wallsmith</user>
+ <email>[email protected]</email>
+ <active>yes</active>
+ </lead>
+ <date>##CURRENT_DATE##</date>
+ <version>
+ <release>##PLUGIN_VERSION##</release>
+ <api>##API_VERSION##</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="http://www.symfony-project.org/license">MIT license</license>
+ <notes>-</notes>
+ <contents>
+ ##CONTENTS##
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.4</min>
+ </php>
+ <pearinstaller>
+ <min>1.4.1</min>
+ </pearinstaller>
+ <package>
+ <name>symfony</name>
+ <channel>pear.symfony-project.com</channel>
+ <min>1.2.0</min>
+ <max>1.3.0</max>
+ <exclude>1.3.0</exclude>
+ </package>
+ </required>
+ </dependencies>
+ <phprelease></phprelease>
+ <changelog>
+ <release>
+ <version>
+ <release>1.0.0</release>
+ <api>1.0.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="http://www.symfony-project.com/license">MIT
license</license>
+ <license>MIT</license>
+ <date>2010-02-17</date>
+ <notes>
+ * Initial plugin release at symfony live 2010
+ </notes>
+ </release>
+ </changelog>
+</package>
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveConnectionManagerTest.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveConnectionManagerTest.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveConnectionManagerTest.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * sfDoctrineMasterSlaveConnectionManager tests.
+ */
+include dirname(__FILE__).'/../../../../../test/bootstrap/unit.php';
+
+$t = new lime_test(19);
+
+$manager = Doctrine_Manager::getInstance();
+$pdo = new
PDO('sqlite://'.sfConfig::get('sf_cache_dir').'/master_slave_test.sqlite');
+
+$conn1 = $manager->openConnection($pdo, 'doctrine');
+$conn2 = $manager->openConnection($pdo, 'slave');
+$conn3 = $manager->openConnection($pdo, 'master');
+
+class sfDoctrineMasterSlaveConnectionManagerTest extends
sfDoctrineMasterSlaveConnectionManager
+{
+ public function getConnections()
+ {
+ return $this->connections;
+ }
+
+ public function setConnections(array $connections)
+ {
+ $this->connections = $connections;
+ }
+}
+
+$connectionManager = new
sfDoctrineMasterSlaveConnectionManagerTest($configuration->getEventDispatcher());
+
+// ->register()
+$t->diag('->register()');
+
+$connectionManager->register($conn1);
+$connections = $connectionManager->getConnections();
+$t->is($connections['default']['master'], 'doctrine', '->register() marks the
first connection as master');
+$t->is(Doctrine_Manager::connection()->getName(), 'doctrine', '->register()
sets the master as the current connection');
+
+$connectionManager->register($conn2);
+$connections = $connectionManager->getConnections();
+$t->is_deeply($connections['default']['slaves'], array('slave'), '->register()
saves additional connections as slaves');
+$t->is(Doctrine_Manager::connection()->getName(), 'doctrine', '->register()
sets the master as the current connection');
+
+$connectionManager->register($conn3, null, true);
+$connections = $connectionManager->getConnections();
+$t->is($connections['default']['master'], 'master', '->register() sets a
connection as master');
+$t->is_deeply($connections['default']['slaves'], array('slave', 'doctrine'),
'->register() fixes master assumptions');
+$t->is(Doctrine_Manager::connection()->getName(), 'master', '->register() sets
the master as the current connection');
+
+// ->getMasterConnection()
+$t->diag('->getMasterConnection()');
+
+$connectionManager->setConnections(array(
+ 'default' => array(
+ 'master' => 'master',
+ 'slaves' => array('slave', 'doctrine'),
+)));
+$t->is($connectionManager->getMasterConnection()->getName(), 'master',
'->getMasterConnection() returns the master connection');
+$t->is($connectionManager->getMasterConnection('default')->getName(),
'master', '->getMasterConnection() accepts a group name');
+$t->is($connectionManager->getMasterConnection($conn3)->getName(), 'master',
'->getMasterConnection() accepts a master connection');
+$t->is($connectionManager->getMasterConnection($conn2)->getName(), 'master',
'->getMasterConnection() accepts a slave connection');
+
+$connectionManager->setConnections(array());
+try
+{
+ $connectionManager->getMasterConnection('default');
+ $t->fail('->getMasterConnection() throws an exception if there is not master
connection');
+}
+catch (Exception $e)
+{
+ $t->pass('->getMasterConnection() throws an exception if there is not master
connection');
+}
+
+$connectionManager->setConnections(array(
+ 'foo' => array('master' => 'master'),
+ 'bar' => array('master' => 'doctrine'),
+));
+$t->is($connectionManager->getMasterConnection()->getName(), 'master',
'->getMasterConnection() defaults to using the first group registered');
+
+// ->getSlaveConnection()
+$t->diag('->getSlaveConnection()');
+
+$connectionManager->setConnections(array(
+ 'default' => array(
+ 'master' => 'master',
+ 'slaves' => array('slave', 'doctrine'),
+)));
+$slave = $connectionManager->getSlaveConnection('default');
+$connections = $connectionManager->getConnections();
+$t->is($slave->getName(), $connections['default']['current_slave'],
'->getSlaveConnection() returns a slave');
+$t->ok(in_array($connections['default']['current_slave'],
$connections['default']['slaves']), '->getSlaveConnection() returns a slave');
+
+$slave = $connectionManager->getSlaveConnection();
+$connections = $connectionManager->getConnections();
+$t->is($slave->getName(), $connections['default']['current_slave'],
'->getSlaveConnection() defaults to the default group');
+
+$conn3->beginTransaction();
+$t->is($connectionManager->getSlaveConnection('default')->getName(), 'master',
'->getSlaveConnection() returns the master if a transaction is open');
+$conn3->rollback();
+$t->is($connectionManager->getSlaveConnection('default')->getName(),
$slave->getName(), '->getSlaveConnection() returns the slave once a transaction
is closed');
+
+$connectionManager->setConnections(array('default' => array(
+ 'master' => 'master',
+ 'slaves' => array(),
+)));
+$t->is($connectionManager->getSlaveConnection('default')->getName(), 'master',
'->getSlaveConnection() returns the master if no slaves are registered');
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveConnectionManagerTest.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added:
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveDebugListenerTest.php
===================================================================
---
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveDebugListenerTest.php
(rev 0)
+++
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveDebugListenerTest.php
2010-02-20 01:11:48 UTC (rev 28144)
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * sfDoctrineMasterSlaveDebugListener tests.
+ */
+include dirname(__FILE__).'/../../../../../test/bootstrap/unit.php';
+
+$t = new lime_test(11);
+
+$manager = Doctrine_Manager::getInstance();
+$pdo = new
PDO('sqlite://'.sfConfig::get('sf_cache_dir').'/master_slave_test.sqlite');
+$master = $manager->openConnection($pdo, 'master');
+$slave = $manager->openConnection($pdo, 'slave');
+
+// ->__construct()
+$t->diag('->__construct()');
+
+try
+{
+ $listener = new sfDoctrineMasterSlaveDebugListener(new sfCallable(null));
+ $t->pass('->__construct() accepts a sfCallable');
+}
+catch (Exception $e)
+{
+ $t->fail('->__construct() accepts a sfCallable');
+ $t->diag(' '.$e->getMessage());
+}
+
+// ->checkConnection()
+$t->diag('->checkConnection()');
+
+$listener = new sfDoctrineMasterSlaveDebugListener($master);
+try
+{
+ $listener->checkConnection($master);
+ $t->pass('->checkConnection() does not throw an exception when passed the
master connection');
+}
+catch (Exception $e)
+{
+ $t->fail('->checkConnection() does not throw an exception when passed the
master connection');
+ $t->diag(' '.$e->getMessage());
+}
+try
+{
+ $listener->checkConnection($slave);
+ $t->fail('->checkConnection() throws an exception when passed a slave
connection');
+}
+catch (Exception $e)
+{
+ $t->pass('->checkConnection() throws an exception when passed a slave
connection');
+}
+
+// ->preExec()
+$t->diag('->preExec()');
+
+try
+{
+ $listener->preExec(new Doctrine_Event($slave, Doctrine_Event::CONN_EXEC,
'SET NAMES ?', array('UTF-8')));
+ $t->pass('->preExec() allows SET queries to a slave connection');
+}
+catch (Exception $e)
+{
+ $t->fail('->preExec() allows SET queries to slave connections');
+ $t->diag(' '.$e->getMessage());
+}
+try
+{
+ $listener->preExec(new Doctrine_Event($slave, Doctrine_Event::CONN_EXEC,
'UPDATE foo SET bar=?', array('test')));
+ $t->fail('->preExec() disallows UPDATE queries to slave connections');
+}
+catch (Exception $e)
+{
+ $t->pass('->preExec() disallows UPDATE queries to slave connections');
+}
+
+// ->prePrepare()
+$t->diag('->prePrepare()');
+
+try
+{
+ $listener->prePrepare(new Doctrine_Event($slave,
Doctrine_Event::CONN_PREPARE, 'SELECT * FROM foo'));
+ $t->pass('->prePrepare() allows SELECT queries to slave connections');
+}
+catch (Exception $e)
+{
+ $t->fail('->prePrepare() allows SELECT queries to slave connections');
+ $t->diag(' '.$e->getMessage());
+}
+try
+{
+ $listener->prePrepare(new Doctrine_Event($slave,
Doctrine_Event::CONN_PREPARE, 'SET NAMES ?', array('UTF-8')));
+ $t->pass('->prePrepare() allows SET queries to slave connections');
+}
+catch (Exception $e)
+{
+ $t->fail('->prePrepare() allows SET queries to slave connections');
+ $t->diag(' '.$e->getMessage());
+}
+try
+{
+ $listener->preExec(new Doctrine_Event($slave, Doctrine_Event::CONN_PREPARE,
'UPDATE foo SET bar=?', array('test')));
+ $t->fail('->prePrepare() disallows UPDATE queries to slave connections');
+}
+catch (Exception $e)
+{
+ $t->pass('->prePrepare() disallows UPDATE queries to slave connections');
+}
+
+// ->preTransactionBegin()
+$t->diag('->preTransactionBegin()');
+
+try
+{
+ $listener->preTransactionBegin(new Doctrine_Event(new
Doctrine_Transaction($slave), Doctrine_Event::TX_BEGIN));
+ $t->fail('->preTransactionBegin() disallows BEGIN TRANSACTION queries to
slave connections');
+}
+catch (Exception $e)
+{
+ $t->pass('->preTransactionBegin() disallows BEGIN TRANSACTION queries to
slave connections');
+}
+
+// ->preTransactionCommit()
+$t->diag('->preTransactionCommit()');
+
+try
+{
+ $listener->preTransactionCommit(new Doctrine_Event(new
Doctrine_Transaction($slave), Doctrine_Event::TX_COMMIT));
+ $t->fail('->preTransactionCommit() disallows COMMIT queries to slave
connections');
+}
+catch (Exception $e)
+{
+ $t->pass('->preTransactionCommit() disallows COMMIT queries to slave
connections');
+}
+
+// ->preTransactionRollback()
+$t->diag('->preTransactionRollback()');
+
+try
+{
+ $listener->preTransactionRollback(new Doctrine_Event(new
Doctrine_Transaction($slave), Doctrine_Event::TX_ROLLBACK));
+ $t->fail('->preTransactionRollback() disallows ROLLBACK queries to slave
connections');
+}
+catch (Exception $e)
+{
+ $t->pass('->preTransactionRollback() disallows ROLLBACK queries to slave
connections');
+}
Property changes on:
plugins/sfDoctrineMasterSlavePlugin/trunk/test/unit/connection/sfDoctrineMasterSlaveDebugListenerTest.php
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
--
You received this message because you are subscribed to the Google Groups
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/symfony-svn?hl=en.