PoolingShardConnection.php 8.19 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17 18 19 20 21
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\DBAL\Sharding;

22 23
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
24
use Doctrine\DBAL\Connection;
25
use Doctrine\DBAL\Driver;
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser;

/**
 * Sharding implementation that pools many different connections
 * internally and serves data from the currently active connection.
 *
 * The internals of this class are:
 *
 * - All sharding clients are specified and given a shard-id during
 *   configuration.
 * - By default, the global shard is selected. If no global shard is configured
 *   an exception is thrown on access.
 * - Selecting a shard by distribution value delegates the mapping
 *   "distributionValue" => "client" to the ShardChooser interface.
 * - An exception is thrown if trying to switch shards during an open
 *   transaction.
 *
 * Instantiation through the DriverManager looks like:
 *
 * @example
 *
 * $conn = DriverManager::getConnection(array(
 *    'wrapperClass' => 'Doctrine\DBAL\Sharding\PoolingShardConnection',
 *    'driver' => 'pdo_mysql',
 *    'global' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''),
 *    'shards' => array(
 *        array('id' => 1, 'user' => 'slave1', 'password', 'host' => '', 'dbname' => ''),
 *        array('id' => 2, 'user' => 'slave2', 'password', 'host' => '', 'dbname' => ''),
 *    ),
 *    'shardChoser' => 'Doctrine\DBAL\Sharding\ShardChoser\MultiTenantShardChoser',
 * ));
 * $shardManager = $conn->getShardManager();
 * $shardManager->selectGlobal();
 * $shardManager->selectShard($value);
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 */
class PoolingShardConnection extends Connection
{
    /**
     * @var array
     */
    private $activeConnections;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
73
     * @var integer
74 75 76 77 78 79 80 81 82
     */
    private $activeShardId;

    /**
     * @var array
     */
    private $connections;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
83 84 85 86 87 88
     * @param array                         $params
     * @param \Doctrine\DBAL\Driver         $driver
     * @param \Doctrine\DBAL\Configuration  $config
     * @param \Doctrine\Common\EventManager $eventManager
     *
     * @throws \InvalidArgumentException
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
     */
    public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null)
    {
        if ( !isset($params['global']) || !isset($params['shards'])) {
            throw new \InvalidArgumentException("Connection Parameters require 'global' and 'shards' configurations.");
        }

        if ( !isset($params['shardChoser'])) {
            throw new \InvalidArgumentException("Missing Shard Choser configuration 'shardChoser'");
        }

        if (is_string($params['shardChoser'])) {
            $params['shardChoser'] = new $params['shardChoser'];
        }

        if ( ! ($params['shardChoser'] instanceof ShardChoser)) {
Steve Müller's avatar
Steve Müller committed
105
            throw new \InvalidArgumentException("The 'shardChoser' configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser");
106 107 108 109 110 111
        }

        $this->connections[0] = array_merge($params, $params['global']);

        foreach ($params['shards'] as $shard) {
            if ( ! isset($shard['id'])) {
Pascal Borreli's avatar
Pascal Borreli committed
112
                throw new \InvalidArgumentException("Missing 'id' for one configured shard. Please specify a unique shard-id.");
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
            }

            if ( !is_numeric($shard['id']) || $shard['id'] < 1) {
                throw new \InvalidArgumentException("Shard Id has to be a non-negative number.");
            }

            if (isset($this->connections[$shard['id']])) {
                throw new \InvalidArgumentException("Shard " . $shard['id'] . " is duplicated in the configuration.");
            }

            $this->connections[$shard['id']] = array_merge($params, $shard);
        }

        parent::__construct($params, $driver, $config, $eventManager);
    }

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
    /**
     * Get active shard id.
     * 
     * @return integer
     */
    public function getActiveShardId()
    {
        return $this->activeShardId;
    }

    /**
     * {@inheritdoc}
     */
    public function getParams()
    {
144
        return $this->activeShardId ? $this->connections[$this->activeShardId] : $this->connections[0];
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
    }

    /**
     * {@inheritdoc}
     */
    public function getHost()
    {
        $params = $this->getParams();

        return isset($params['host']) ? $params['host'] : parent::getHost();
    }

    /**
     * {@inheritdoc}
     */
    public function getPort()
    {
        $params = $this->getParams();

        return isset($params['port']) ? $params['port'] : parent::getPort();
    }

    /**
     * {@inheritdoc}
     */
    public function getUsername()
    {
        $params = $this->getParams();

        return isset($params['user']) ? $params['user'] : parent::getUsername();
    }

    /**
     * {@inheritdoc}
     */
    public function getPassword()
    {
        $params = $this->getParams();

        return isset($params['password']) ? $params['password'] : parent::getPassword();
    }

187
    /**
Benjamin Morel's avatar
Benjamin Morel committed
188
     * Connects to a given shard.
189 190
     *
     * @param mixed $shardId
Benjamin Morel's avatar
Benjamin Morel committed
191 192 193 194
     *
     * @return boolean
     *
     * @throws \Doctrine\DBAL\Sharding\ShardingException
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
     */
    public function connect($shardId = null)
    {
        if ($shardId === null && $this->_conn) {
            return false;
        }

        if ($shardId !== null && $shardId === $this->activeShardId) {
            return false;
        }

        if ($this->getTransactionNestingLevel() > 0) {
            throw new ShardingException("Cannot switch shard when transaction is active.");
        }

210
        $this->activeShardId = (int)$shardId;
211 212 213

        if (isset($this->activeConnections[$this->activeShardId])) {
            $this->_conn = $this->activeConnections[$this->activeShardId];
214

215 216 217 218 219 220
            return false;
        }

        $this->_conn = $this->activeConnections[$this->activeShardId] = $this->connectTo($this->activeShardId);

        if ($this->_eventManager->hasListeners(Events::postConnect)) {
221
            $eventArgs = new ConnectionEventArgs($this);
222 223 224 225 226 227 228
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
        }

        return true;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
229 230 231
     * Connects to a specific connection.
     *
     * @param string $shardId
232
     *
Benjamin Morel's avatar
Benjamin Morel committed
233
     * @return \Doctrine\DBAL\Driver\Connection
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
     */
    protected function connectTo($shardId)
    {
        $params = $this->getParams();

        $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array();

        $connectionParams = $this->connections[$shardId];

        $user = isset($connectionParams['user']) ? $connectionParams['user'] : null;
        $password = isset($connectionParams['password']) ? $connectionParams['password'] : null;

        return $this->_driver->connect($connectionParams, $user, $password, $driverOptions);
    }

Benjamin Morel's avatar
Benjamin Morel committed
249 250 251 252 253
    /**
     * @param string|null $shardId
     *
     * @return boolean
     */
254 255 256
    public function isConnected($shardId = null)
    {
        if ($shardId === null) {
257
            return $this->_conn !== null;
258 259 260 261 262
        }

        return isset($this->activeConnections[$shardId]);
    }

Benjamin Morel's avatar
Benjamin Morel committed
263 264 265
    /**
     * @return void
     */
266 267
    public function close()
    {
268 269
        $this->_conn             = null;
        $this->activeConnections = null;
270
        $this->activeShardId     = null;
271 272
    }
}