Skip to content

Commit

Permalink
Added automatic separation of read / write connections into database …
Browse files Browse the repository at this point in the history
…layer.
  • Loading branch information
taylorotwell committed Nov 25, 2013
1 parent 5d48577 commit a3b0e87
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 4 deletions.
36 changes: 34 additions & 2 deletions src/Illuminate/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ class Connection implements ConnectionInterface {
*/
protected $pdo;

/**
* The active PDO connection used for reads.
*
* @var PDO
*/
protected $readPdo;

/**
* The query grammar implementation.
*
Expand Down Expand Up @@ -268,7 +275,7 @@ public function select($query, $bindings = array())
// For select statements, we'll simply execute the query and return an array
// of the database result set. Each element in the array will be a single
// row from the database table, and will either be an array or objects.
$statement = $me->getPdo()->prepare($query);
$statement = $me->getReadPdo()->prepare($query);

$statement->execute($me->prepareBindings($bindings));

Expand Down Expand Up @@ -635,15 +642,40 @@ public function getPdo()
return $this->pdo;
}

/**
* Get the current PDO connection used for reading.
*
* @return PDO
*/
public function getReadPdo()
{
return $this->readPdo ?: $this->pdo;
}

/**
* Set the PDO connection.
*
* @param PDO $pdo
* @return void
* @return \Illuminate\Database\Connection
*/
public function setPdo(PDO $pdo)
{
$this->pdo = $pdo;

return $this;
}

/**
* Set the PDO connection used for reading.
*
* @param PDO $pdo
* @return \Illuminate\Database\Connection
*/
public function setReadPdo(PDO $pdo)
{
$this->readPdo = $pdo;

return $this;
}

/**
Expand Down
101 changes: 101 additions & 0 deletions src/Illuminate/Database/Connectors/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,112 @@ public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);

if (isset($config['read']))
{
return $this->createReadWriteConnection($config);
}
else
{
return $this->createSingleConnection($config);
}
}

/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createSingleConnection(array $config)
{
$pdo = $this->createConnector($config)->connect($config);

return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config);
}

/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createReadWriteConnection(array $config)
{
$connection = $this->createSingleConnection($this->getWriteConfig($config));

return $connection->setReadPdo($this->createReadPdo($config));
}

/**
* Create a new PDO instance for reading.
*
* @param array $config
* @return \PDO
*/
protected function createReadPdo(array $config)
{
$readConfig = $this->getReadConfig($config);

return $this->createConnector($readConfig)->connect($readConfig);
}

/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getReadConfig(array $config)
{
$readConfig = $this->getReadWriteConfig($config, 'read');

return $this->mergeReadWriteConfig($config, $readConfig);
}

/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getWriteConfig(array $config)
{
$writeConfig = $this->getReadWriteConfig($config, 'write');

return $this->mergeReadWriteConfig($config, $writeConfig);
}

/**
* Get a read / write level configuration.
*
* @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{
if (isset($config[$type][0]))
{
return $config[$type][array_rand($config[$type])];
}
else
{
return $config[$type];
}
}

/**
* Merge a configuration for a read / write connection.
*
* @param array $config
* @param array $merge
* @return array
*/
protected function mergeReadWriteConfig(array $config, array $merge)
{
return array_except(array_merge($config, $merge), array('read', 'write'));
}

/**
* Parse and prepare the database configuration.
*
Expand Down
3 changes: 2 additions & 1 deletion src/Illuminate/Foundation/changes.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
{"message": "Added support for checking job attempts to Iron.io queue jobs.", "backport": null},
{"message": "Added support for releasing pushed Iron.io jobs back onto the queue.", "backport": null},
{"message": "Allow strict mode option to be enabled for MySQL connections.", "backport": null},
{"message": "Added 'wherePivot' and 'orWherePivot' methods to BelongsToMany relationship for convenience.", "backport": null}
{"message": "Added 'wherePivot' and 'orWherePivot' methods to BelongsToMany relationship for convenience.", "backport": null},
{"message": "Added automatic separation of read / write connections into database layer.", "backport": null}
],
"4.0.x": [
{"message": "Added implode method to query builder and Collection class.", "backport": null},
Expand Down
27 changes: 27 additions & 0 deletions tests/Database/DatabaseConnectionFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,33 @@ public function testMakeCallsCreateConnection()
}


public function testMakeCallsCreateConnectionForReadWrite()
{
$factory = $this->getMock('Illuminate\Database\Connectors\ConnectionFactory', array('createConnector', 'createConnection'), array($container = m::mock('Illuminate\Container\Container')));
$container->shouldReceive('bound')->andReturn(false);
$connector = m::mock('stdClass');
$config = array(
'read' => array('database' => 'database'),
'write' => array('database' => 'database'),
'driver' => 'mysql', 'prefix' => 'prefix', 'name' => 'foo'
);
$expect = $config;
unset($expect['read']);
unset($expect['write']);
$expect['database'] = 'database';
$pdo = new DatabaseConnectionFactoryPDOStub;
$connector->shouldReceive('connect')->twice()->with($expect)->andReturn($pdo);
$factory->expects($this->exactly(2))->method('createConnector')->with($expect)->will($this->returnValue($connector));
$mockConnection = m::mock('stdClass');
$mockConnection->shouldReceive('setReadPdo')->once()->andReturn($mockConnection);
$passedConfig = array_merge($expect, array('name' => 'foo'));
$factory->expects($this->once())->method('createConnection')->with($this->equalTo('mysql'), $this->equalTo($pdo), $this->equalTo('database'), $this->equalTo('prefix'), $this->equalTo($passedConfig))->will($this->returnValue($mockConnection));
$connection = $factory->make($config, 'foo');

$this->assertEquals($mockConnection, $connection);
}


public function testMakeCanCallTheContainer()
{
$factory = $this->getMock('Illuminate\Database\Connectors\ConnectionFactory', array('createConnector'), array($container = m::mock('Illuminate\Container\Container')));
Expand Down
5 changes: 4 additions & 1 deletion tests/Database/DatabaseConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ public function testSelectOneCallsSelectAndReturnsSingleResult()
public function testSelectProperlyCallsPDO()
{
$pdo = $this->getMock('DatabaseConnectionTestMockPDO', array('prepare'));
$writePdo = $this->getMock('DatabaseConnectionTestMockPDO', array('prepare'));
$writePdo->expects($this->never())->method('prepare');
$statement = $this->getMock('PDOStatement', array('execute', 'fetchAll'));
$statement->expects($this->once())->method('execute')->with($this->equalTo(array('foo' => 'bar')));
$statement->expects($this->once())->method('fetchAll')->will($this->returnValue(array('boom')));
$pdo->expects($this->once())->method('prepare')->with('foo')->will($this->returnValue($statement));
$mock = $this->getMockConnection(array('prepareBindings'), $pdo);
$mock = $this->getMockConnection(array('prepareBindings'), $writePdo);
$mock->setReadPdo($pdo);
$mock->expects($this->once())->method('prepareBindings')->with($this->equalTo(array('foo' => 'bar')))->will($this->returnValue(array('foo' => 'bar')));
$results = $mock->select('foo', array('foo' => 'bar'));
$this->assertEquals(array('boom'), $results);
Expand Down

0 comments on commit a3b0e87

Please sign in to comment.