Completed
Pull Request — master (#35)
by Mathieu
01:57
created

MasterSlavesConnection::wrap()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 10
rs 9.4286
cc 3
eloc 7
nc 3
nop 0
1
<?php
2
3
namespace Ez\DbLinker\Driver\Connection;
4
5
use Doctrine\DBAL\Driver\Connection;
6
use SplObjectStorage;
7
use Exception;
8
9
class MasterSlavesConnection implements Connection, ConnectionWrapper
10
{
11
    private $master;
12
    private $slavesWeights;
13
    private $connection;
14
    private $driver;
15
    private $currentConnectionFactory;
16
17
    /**
18
     * @param Closure          $master
19
     * @param SplObjectStorage $slavesWeights
20
     */
21
    public function __construct($master, SplObjectStorage $slavesWeights)
22
    {
23
        $this->master = $master;
24
        $this->checkSlavesWeights($slavesWeights);
25
        $this->slavesWeights = $slavesWeights;
26
    }
27
28
    private function checkSlavesWeights(SplObjectStorage $slavesWeights)
29
    {
30
        foreach ($slavesWeights as $slave) {
31
            if ((int)$slavesWeights[$slave] < 0) {
32
                throw new Exception('Slave weight must be >= 0');
33
            }
34
        }
35
    }
36
37
    public function connectToMaster()
38
    {
39
        $this->currentConnectionFactory = $this->master;
40
        $this->connection = null;
41
    }
42
43
    public function connectToSlave()
44
    {
45
        $this->currentConnectionFactory = null;
46
        $this->connection = null;
47
    }
48
49
    public function isConnectedToMaster()
50
    {
51
        return $this->currentConnectionFactory === $this->master;
52
    }
53
54
    private function connection()
55
    {
56
        if ($this->connection === null) {
57
            $this->wrap();
58
        }
59
        return $this->connection;
60
    }
61
62
    /**
63
     * @inherit
64
     */
65
    public function getCurrentConnection()
66
    {
67
        return $this->wrappedConnection();
68
    }
69
70
    /**
71
     * @inherit
72
     */
73
    public function wrappedConnection()
74
    {
75
        if ($this->connection === null) {
76
            $this->wrap();
77
        }
78
        return $this->connection;
79
    }
80
81
    public function wrappedDriver()
82
    {
83
        if ($this->driver === null) {
84
            $this->wrap();
85
        }
86
        return $this->driver;
87
    }
88
89
    private function wrap()
90
    {
91
        if ($this->currentConnectionFactory === null) {
92
            $this->currentConnectionFactory = $this->chooseASlave() ?: $this->master;
93
        }
94
        $factory = $this->currentConnectionFactory;
95
        $connection = $factory();
96
        $this->connection = $connection->getWrappedConnection();
97
        $this->driver = $connection->getDriver();
98
    }
99
100
    private function chooseASlave()
101
    {
102
        $totalSlavesWeight = $this->totalSlavesWeight();
103
        if ($totalSlavesWeight < 1) {
104
            return null;
105
        }
106
        $weightTarget = mt_rand(1, $totalSlavesWeight);
107
        foreach ($this->slavesWeights as $slave) {
108
            $weightTarget -= $this->slavesWeights[$slave];
109
            if ($weightTarget <= 0) {
110
                return $slave;
111
            }
112
        }
113
    }
114
115
    private function totalSlavesWeight()
116
    {
117
        $weight = 0;
118
        foreach ($this->slavesWeights as $slave) {
119
            $weight += $this->slavesWeights[$slave];
120
        }
121
        return $weight;
122
    }
123
124
    public function disableCurrentSlave()
125
    {
126
        $this->slavesWeights->detach($this->currentConnectionFactory);
127
        $this->currentConnectionFactory = null;
128
        $this->connection = null;
129
    }
130
131
    public function slaves()
132
    {
133
        return clone $this->slavesWeights;
134
    }
135
136
    /**
137
     * Prepares a statement for execution and returns a Statement object.
138
     *
139
     * @param string $prepareString
140
     *
141
     * @return \Doctrine\DBAL\Driver\Statement
142
     */
143
    public function prepare($prepareString)
144
    {
145
        $this->connectToMaster();
146
        return $this->wrappedConnection()->prepare($prepareString);
147
    }
148
149
    /**
150
     * Executes an SQL statement, returning a result set as a Statement object.
151
     *
152
     * @return \Doctrine\DBAL\Driver\Statement
153
     */
154
    public function query()
155
    {
156
        return call_user_func_array([$this->wrappedConnection(), __FUNCTION__], func_get_args());
157
    }
158
159
    /**
160
     * Quotes a string for use in a query.
161
     *
162
     * @param string  $input
163
     * @param integer $type
164
     *
165
     * @return string
166
     */
167
    public function quote($input, $type = \PDO::PARAM_STR)
168
    {
169
        return $this->wrappedConnection()->quote($input, $type);
170
    }
171
172
    /**
173
     * Executes an SQL statement and return the number of affected rows.
174
     *
175
     * @param string $statement
176
     *
177
     * @return integer
178
     */
179
    public function exec($statement)
180
    {
181
        $this->connectToMaster();
182
        return $this->wrappedConnection()->exec($statement);
183
    }
184
185
    /**
186
     * Returns the ID of the last inserted row or sequence value.
187
     *
188
     * @param string|null $name
189
     *
190
     * @return string
191
     */
192
    public function lastInsertId($name = null)
193
    {
194
        return $this->wrappedConnection()->lastInsertId($name);
195
    }
196
197
    /**
198
     * Initiates a transaction.
199
     *
200
     * @return boolean TRUE on success or FALSE on failure.
201
     */
202
    public function beginTransaction()
203
    {
204
        $this->connectToMaster();
205
        return $this->wrappedConnection()->beginTransaction();
206
    }
207
208
    /**
209
     * Commits a transaction.
210
     *
211
     * @return boolean TRUE on success or FALSE on failure.
212
     */
213
    public function commit()
214
    {
215
        $this->connectToMaster();
216
        return $this->wrappedConnection()->commit();
217
    }
218
219
    /**
220
     * Rolls back the current transaction, as initiated by beginTransaction().
221
     *
222
     * @return boolean TRUE on success or FALSE on failure.
223
     */
224
    public function rollBack()
225
    {
226
        $this->connectToMaster();
227
        return $this->wrappedConnection()->rollBack();
228
    }
229
230
    /**
231
     * Returns the error code associated with the last operation on the database handle.
232
     *
233
     * @return string|null The error code, or null if no operation has been run on the database handle.
234
     */
235
    public function errorCode()
236
    {
237
        return $this->wrappedConnection()->errorCode();
238
    }
239
240
    /**
241
     * Returns extended error information associated with the last operation on the database handle.
242
     *
243
     * @return array
244
     */
245
    public function errorInfo()
246
    {
247
        return $this->wrappedConnection()->errorInfo();
248
    }
249
}
250