2.6.2. Connection Locator

Some applications may use multiple database servers; for example, one for writes, and one or more for reads. The ConnectionLocator allows you to define multiple Connection objects for lazy-loaded read and write connections. It will create the connections only when they are when called. The Connection creation logic should be wrapped in factory callable.

2.6.2.1. Instantiation

The easiest way to create a ConnectionLocator is to use its static new() method, either with PDO connection arguments, or with an actual PDO instance:

use Atlas\Pdo\ConnectionLocator;

// pass PDO constructor arguments ...
$connectionLocator = ConnectionLocator::new(
    'mysql:host=localhost;dbname=testdb',
    'username',
    'password'
);

// ... or a PDO instance.
$connectionLocator = ConnectionLocator::new($pdo);

Doing so will define the default connection factory for the ConnectionLocator.

2.6.2.2. Runtime Configuration

Once you have a ConnectionLocator, you can add as many named read and write connection factories as you like:

// the write (master) server
$connectionLocator->setWriteFactory('master', Connection::factory(
    'mysql:host=master.db.localhost;dbname=database',
    'username',
    'password'
));

// read (slave) #1
$connectionLocator->setReadFactory('slave1', Connection::factory(
    'mysql:host=slave1.db.localhost;dbname=database',
    'username',
    'password'
));

// read (slave) #2
$connectionLocator->setReadFactory('slave2', Connection::factory(
    'mysql:host=slave2.db.localhost;dbname=database',
    'username',
    'password'
));

// read (slave) #3
$connectionLocator->setReadFactory('slave3', Connection::factory(
    'mysql:host=slave3.db.localhost;dbname=database',
    'username',
    'password'
));

2.6.2.3. Getting Connections

Retrieve a Connection from the locator when you need it. This will create the Connection (if needed) and then return it.

  • getDefault() will return the default Connection.

  • getRead() will return a random read Connection; after the first call, getRead() will always return the same Connection. (If no read Connections are defined, it will return the default connection.)

  • getWrite() will return a random write Connection; after the first call, getWrite() will always return the same Connection. (If no write Connections are defined, it will return the default connection.)

$read = $connectionLocator->getRead();
$results = $read->fetchAll('SELECT * FROM table_name LIMIT 10');

$readAgain = $connectionLocator->getRead();
assert($read === $readAgain); // true

You can get any read or write connection directly by name using the get() method:

$foo = $connectionLocator->get(ConnectionLocator::READ, 'foo');
$bar = $connectionLocator->get(ConnectionLocator::WRITE, 'bar');

2.6.2.4. Locking To The Write Connection

If you call the lockToWrite() method, calls to getRead() will return the write connection instead of the read connection.

$read = $connectionLocator->getRead();
$write = $connectionLocator->getWrite();

$connectionLocator->lockToWrite();
$readAgain = $connectionLocator->getRead();
assert($readAgain === $write); // true

You can disable the lock-to-write behavior by calling lockToWrite(false).

2.6.2.5. Construction-Time Configuration

The ConnectionLocator can be configured with all its connections at construction time; this can be useful with dependency injection mechanisms. (Note that this requires using the constructor proper, not the static new() method.)

use Atlas\Pdo\Connection;
use Atlas\Pdo\ConnectionLocator;

// default connection
$default = Connection::factory(
    'mysql:host=default.db.localhost;dbname=database',
    'username',
    'password'
);

// read connections
$read = [
    'slave1' => Connection::factory(
        'mysql:host=slave1.db.localhost;dbname=database',
        'username',
        'password'
    ),
    'slave2' => Connection::factory(
        'mysql:host=slave2.db.localhost;dbname=database',
        'username',
        'password'
    ),
    'slave3' => Connection::factory(
        'mysql:host=slave3.db.localhost;dbname=database',
        'username',
        'password'
    ),
];

// write connection
$write = [
    'master' => Connection::factory(
        'mysql:host=master.db.localhost;dbname=database',
        'username',
        'password'
    ),
];

// configure locator at construction time
$connectionLocator = new ConnectionLocator($default, $read, $write);

2.6.2.6. Query Logging

As with an individual Connection, it is sometimes useful to log all queries on all connections in the ConnectionLocator. To do so, call its logQueries() method, issue your queries, and then call getQueries() to get back the log entries.

// start logging
$connectionLocator->logQueries(true);

// retrieve connections and issue queries, then:
$queries = $connectionLocator->getQueries();

// stop logging
$connectionLocator->logQueries(false);

Each query log entry will have one added key, connection, indicating which connection performed the query. The connection label will be DEFAULT for the default connection, READ: and the read connection name, or WRITE: and the write connection name.

Note:

Calling logQueries() will turn logging on and off for all instances in the locator, even if those instances are not "in hand" at the moment. That is, you do not have to re-get the instance; logging for each connection will be turned on and off "at a distance."

You may wish to set a custom logger on the ConnectionLocator. To do so, call setQueryLogger() and pass a callable with the signature function (array $entry) : void.

class CustomDebugger
{
    public function __invoke(array $entry) : void
    {
        // call an injected logger to record the entry
    }
}

$customDebugger = new CustomDebugger();
$connectionLocator->setQueryLogger($customDebugger);
$connectionLocator->logQueries(true);

// now the Connection will send query log entries to the CustomDebugger

Note:

If you set a custom logger, the Connection will no longer retain its own query log entries; they will all go to the custom logger. This means that getQueries() on the Connection not show any new entries.