Failed Conditions
Pull Request — develop (#3348)
by Sergei
60:53
created

PoolingShardConnection::getPort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Sharding;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\DBAL\Configuration;
9
use Doctrine\DBAL\Connection;
10
use Doctrine\DBAL\Driver;
11
use Doctrine\DBAL\Driver\Connection as DriverConnection;
12
use Doctrine\DBAL\Event\ConnectionEventArgs;
13
use Doctrine\DBAL\Events;
14
use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser;
15
use InvalidArgumentException;
16
use function array_merge;
17
use function is_numeric;
18
use function is_string;
19
use function sprintf;
20
21
/**
22
 * Sharding implementation that pools many different connections
23
 * internally and serves data from the currently active connection.
24
 *
25
 * The internals of this class are:
26
 *
27
 * - All sharding clients are specified and given a shard-id during
28
 *   configuration.
29
 * - By default, the global shard is selected. If no global shard is configured
30
 *   an exception is thrown on access.
31
 * - Selecting a shard by distribution value delegates the mapping
32
 *   "distributionValue" => "client" to the ShardChoser interface.
33
 * - An exception is thrown if trying to switch shards during an open
34
 *   transaction.
35
 *
36
 * Instantiation through the DriverManager looks like:
37
 *
38
 * @example
39
 *
40
 * $conn = DriverManager::getConnection(array(
41
 *    'wrapperClass' => 'Doctrine\DBAL\Sharding\PoolingShardConnection',
42
 *    'driver' => 'pdo_mysql',
43
 *    'global' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''),
44
 *    'shards' => array(
45
 *        array('id' => 1, 'user' => 'slave1', 'password', 'host' => '', 'dbname' => ''),
46
 *        array('id' => 2, 'user' => 'slave2', 'password', 'host' => '', 'dbname' => ''),
47
 *    ),
48
 *    'shardChoser' => 'Doctrine\DBAL\Sharding\ShardChoser\MultiTenantShardChoser',
49
 * ));
50
 * $shardManager = $conn->getShardManager();
51
 * $shardManager->selectGlobal();
52
 * $shardManager->selectShard($value);
53
 */
54
class PoolingShardConnection extends Connection
55
{
56
    /** @var DriverConnection[] */
57
    private $activeConnections = [];
58
59
    /** @var string|int|null */
60
    private $activeShardId;
61
62
    /** @var mixed[] */
63
    private $connectionParameters = [];
64
65
    /**
66
     * {@inheritDoc}
67
     *
68
     * @throws InvalidArgumentException
69 407
     */
70
    public function __construct(
71 407
        array $params,
72 329
        Driver $driver,
73
        ?Configuration $config = null,
74
        ?EventManager $eventManager = null
75 403
    ) {
76 277
        if (! isset($params['global'], $params['shards'])) {
77
            throw new InvalidArgumentException('Connection Parameters require "global" and "shards" configurations.');
78
        }
79 401
80 399
        if (! isset($params['shardChoser'])) {
81
            throw new InvalidArgumentException('Missing Shard Choser configuration "shardChoser".');
82
        }
83 401
84 252
        if (is_string($params['shardChoser'])) {
85
            $params['shardChoser'] = new $params['shardChoser']();
86
        }
87 399
88
        if (! ($params['shardChoser'] instanceof ShardChoser)) {
89 399
            throw new InvalidArgumentException('The "shardChoser" configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser');
90 399
        }
91 202
92
        $this->connectionParameters[0] = array_merge($params, $params['global']);
93
94 397
        foreach ($params['shards'] as $shard) {
95 227
            if (! isset($shard['id'])) {
96
                throw new InvalidArgumentException('Missing "id" for one configured shard. Please specify a unique shard-id.');
97
            }
98 395
99 177
            if (! is_numeric($shard['id']) || $shard['id'] < 1) {
100
                throw new InvalidArgumentException('Shard Id has to be a non-negative number.');
101
            }
102 395
103
            if (isset($this->connectionParameters[$shard['id']])) {
104
                throw new InvalidArgumentException(sprintf('Shard "%s" is duplicated in the configuration.', $shard['id']));
105 393
            }
106 393
107
            $this->connectionParameters[$shard['id']] = array_merge($params, $shard);
108
        }
109
110
        parent::__construct($params, $driver, $config, $eventManager);
111
    }
112
113 127
    /**
114
     * Get active shard id.
115 127
     *
116
     * @return string|int|null
117
     */
118
    public function getActiveShardId()
119
    {
120
        return $this->activeShardId;
121 393
    }
122
123 393
    /**
124
     * {@inheritdoc}
125
     */
126
    public function getParams() : array
127
    {
128
        return $this->activeShardId ? $this->connectionParameters[$this->activeShardId] : $this->connectionParameters[0];
129 77
    }
130
131 77
    /**
132
     * Connects to a given shard.
133 77
     *
134
     * @param string|int|null $shardId
135
     *
136
     * @throws ShardingException
137
     */
138
    public function connect($shardId = null) : void
139 52
    {
140
        if ($shardId === null && $this->_conn) {
141 52
            return;
142
        }
143 52
144
        if ($shardId !== null && $shardId === $this->activeShardId) {
145
            return;
146
        }
147
148
        if ($this->getTransactionNestingLevel() > 0) {
149 52
            throw new ShardingException('Cannot switch shard when transaction is active.');
150
        }
151 52
152
        $activeShardId = $this->activeShardId = (int) $shardId;
153 52
154
        if (isset($this->activeConnections[$activeShardId])) {
155
            $this->_conn = $this->activeConnections[$activeShardId];
156
157
            return;
158
        }
159 27
160
        $this->_conn = $this->activeConnections[$activeShardId] = $this->connectTo($activeShardId);
161 27
162
        if (! $this->_eventManager->hasListeners(Events::postConnect)) {
163 27
            return;
164
        }
165
166
        $eventArgs = new ConnectionEventArgs($this);
167
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
168
    }
169
170
    /**
171
     * Connects to a specific connection.
172
     *
173 366
     * @param string|int $shardId
174
     */
175 366
    protected function connectTo($shardId) : DriverConnection
176 352
    {
177
        $params = $this->getParams();
178
179 366
        $driverOptions = $params['driverOptions'] ?? [];
180
181
        $connectionParams = $this->connectionParameters[$shardId];
182
183 366
        $user     = $connectionParams['user'] ?? '';
184 152
        $password = $connectionParams['password'] ?? '';
185
186
        return $this->_driver->connect($connectionParams, $user, $password, $driverOptions);
187 366
    }
188
189 366
    /**
190
     * @param string|int|null $shardId
191
     */
192
    public function isConnected($shardId = null) : bool
193
    {
194
        if ($shardId === null) {
195 366
            return $this->_conn !== null;
196
        }
197 366
198 366
        return isset($this->activeConnections[$shardId]);
199
    }
200
201
    public function close() : void
202
    {
203
        $this->_conn             = null;
204
        $this->activeConnections = [];
205
        $this->activeShardId     = null;
206
    }
207
}
208