Completed
Pull Request — master (#49)
by
unknown
53:18
created

MasterSlavesConnection::getLastConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Ez\DbLinker\Driver\Connection;
4
5
use Doctrine\DBAL\Driver\Connection;
6
use Ez\DbLinker\ExtendedServer;
7
use Ez\DbLinker\Master;
8
use Ez\DbLinker\Slave;
9
10
class MasterSlavesConnection implements Connection
11
{
12
    /**
13
     * configuration
14
     *
15
     * @var Master
16
     */
17
    private $master;
18
19
    /**
20
     * configuration
21
     *
22
     * @var Slave[]
23
     */
24
    private $slaves;
25
26
    /**
27
     * @var \Ez\DbLinker\Cache
28
     */
29
    private $cache;
30
31
    /**
32
     * @var bool
33
     */
34
    private $forceMaster = false;
35
36
    /**
37
     * @var int
38
     */
39
    private $maxSlaveDelay = 30;
40
41
    /**
42
     * @var int
43
     */
44
    private $slaveStatusCacheTtl = 10;
45
46
    /**
47
     * @var ExtendedServer
48
     */
49
    private $lastConnection;
50
51
    public function __construct(array $master, array $slaves, $cache = null)
52
    {
53
        $this->master = new Master($master);
54
        $this->slaves = $this->filteredSlaves($slaves);
55
        $this->cache = $cache;
56
    }
57
58
    public function disableCache() {
59
        $this->cache->disableCache();
60
    }
61
62
    /**
63
     * @param array $slaves
64
     * @return Slave[]
65
     */
66
    private function filteredSlaves(array $slaves)
67
    {
68
        // keep slave with weight > 0
69
        return array_filter(array_map(function ($slave) {
70
            if ((int) $slave['weight'] > 0) {
71
                return new Slave($slave);
72
            }
73
            return null;
74
        }, $slaves));
75
    }
76
77
    /**
78
     * @param bool|null $forced
79
     * @return Connection
80
     * @throws \Doctrine\DBAL\DBALException
81
     */
82
    private function masterConnection(bool $forced = null): Connection
83
    {
84
        if ($forced !== null) {
85
            $this->forceMaster = $forced;
86
        }
87
        $this->lastConnection = $this->master;
88
        return $this->master->connection();
89
    }
90
91
    /**
92
     * @return Connection
93
     * @throws \Doctrine\DBAL\DBALException
94
     */
95
    private function slaveConnection(): ?Connection
96
    {
97
        if (empty($this->slaves)) {
98
            return $this->masterConnection();
99
        }
100
101
        $this->forceMaster = false;
102
        $x = null;
103
104
        // if we have a connected slave
105
        foreach ($this->slaves as $slave) {
106
            if ($slave->isConnected()) {
107
                $x = $slave;
108
            }
109
        }
110
        if (!$x) {
111
            $x = $this->randomSlave($this->slaves);
112
        }
113
114
        $this->lastConnection = $x;
115
        return $x->connection();
116
    }
117
118
    /**
119
     * @param Slave[] $slaves
120
     * @return Slave
121
     */
122
    private function randomSlave(array $slaves): Slave
123
    {
124
        $weights = [];
125
        foreach ($slaves as $slaveIndex => $slave) {
126
            if (!$this->isSlaveOk($slave)) {
127
                continue;
128
            }
129
            $weights = array_merge($weights, array_fill(0, $slave->getWeight(), $slaveIndex));
130
        }
131
        return $slaves[$weights[array_rand($weights)]];
132
    }
133
134
    /**
135
     * @return ExtendedServer|null
136
     */
137
    public function getLastConnection(): ?ExtendedServer
138
    {
139
        return $this->lastConnection;
140
    }
141
142
    /**
143
     * @return Slave[]|null
144
     */
145
    public function slaves(): ?array
146
    {
147
        return $this->slaves;
148
    }
149
150
    /**
151
     * Prepares a statement for execution and returns a Statement object.
152
     *
153
     * @param string $prepareString
154
     *
155
     * @return \Doctrine\DBAL\Driver\Statement
156
     */
157
    public function prepare($prepareString)
158
    {
159
        $cnx = null;
0 ignored issues
show
Unused Code introduced by
$cnx is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
160
        $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
161
162
        if ($this->forceMaster || $caller === 'executeUpdate') {
163
            $cnx = $this->masterConnection();
164
        } else if ($caller === 'executeQuery') {
165
            if (preg_match('/\b(DELETE|UPDATE|INSERT|REPLACE)\b/i', $prepareString)) {
166
                $cnx = $this->masterConnection();
167
                error_log('should call executeUpdate()');
168
            } else {
169
                $cnx = $this->slaveConnection();
170
            }
171
        } else if (preg_match('/\b(DELETE|UPDATE|INSERT|REPLACE)\b/i', $prepareString)) {
172
            $cnx = $this->masterConnection();
173
        } else {
174
            $cnx = $this->slaveConnection();
175
        }
176
177
        return $cnx->prepare($prepareString);
178
    }
179
180
    public function forceMaster(bool $force): void
181
    {
182
        $this->forceMaster = $force;
183
    }
184
185
    /**
186
     * Executes an SQL statement, returning a result set as a Statement object.
187
     *
188
     * @return \Doctrine\DBAL\Driver\Statement
189
     */
190
    public function query()
191
    {
192
        $cnx = $this->forceMaster ? $this->masterConnection() : $this->slaveConnection();
193
        return call_user_func_array([$cnx, __FUNCTION__], func_get_args());
194
    }
195
196
    /**
197
     * Quotes a string for use in a query.
198
     *
199
     * @param string  $input
200
     * @param integer $type
201
     *
202
     * @return string
203
     */
204
    public function quote($input, $type = \PDO::PARAM_STR)
205
    {
206
        return $this->slaveConnection()->quote($input, $type);
207
    }
208
209
    /**
210
     * Executes an SQL statement and return the number of affected rows.
211
     *
212
     * @param string $statement
213
     *
214
     * @return integer
215
     */
216
    public function exec($statement)
217
    {
218
        return $this->masterConnection()->exec($statement);
219
    }
220
221
    /**
222
     * Returns the ID of the last inserted row or sequence value.
223
     *
224
     * @param string|null $name
225
     *
226
     * @return string
227
     */
228
    public function lastInsertId($name = null)
229
    {
230
        return $this->masterConnection()->lastInsertId($name);
231
    }
232
233
    /**
234
     * Initiates a transaction.
235
     *
236
     * @return boolean TRUE on success or FALSE on failure.
237
     */
238
    public function beginTransaction()
239
    {
240
        return $this->masterConnection(true)->beginTransaction();
241
    }
242
243
    /**
244
     * Commits a transaction.
245
     *
246
     * @return boolean TRUE on success or FALSE on failure.
247
     */
248
    public function commit()
249
    {
250
        return $this->masterConnection(false)->commit();
251
    }
252
253
    /**
254
     * Rolls back the current transaction, as initiated by beginTransaction().
255
     *
256
     * @return boolean TRUE on success or FALSE on failure.
257
     */
258
    public function rollBack()
259
    {
260
        return $this->masterConnection(false)->rollBack();
261
    }
262
263
    /**
264
     * Returns the error code associated with the last operation on the database handle.
265
     *
266
     * @return string|null The error code, or null if no operation has been run on the database handle.
267
     */
268
    public function errorCode()
269
    {
270
        return $this->lastConnection->connection()->errorCode();
271
    }
272
273
    /**
274
     * Returns extended error information associated with the last operation on the database handle.
275
     *
276
     * @return array
277
     */
278
    public function errorInfo()
279
    {
280
        return $this->lastConnection->connection()->errorInfo();
281
    }
282
283
    private function hasCache() {
284
        return $this->cache !== null;
285
    }
286
287
    private function getCacheKey(Slave $slave) {
288
        return 'Slave_' . md5(serialize($slave->dbalConfig()));
289
    }
290
291
    public function setSlaveStatus(Slave $slave, bool $running, int $delay = null) {
292
        if ($this->hasCache()) {
293
            $this->cache->setCacheItem($this->getCacheKey($slave), ['running' => $running, 'delay' => $delay], $this->slaveStatusCacheTtl);
294
        }
295
        return ['running' => $running, 'delay' => $delay];
296
    }
297
298
    private function isSlaveOK(Slave $slave): bool {
299
        if ($this->hasCache()) {
300
            $data = $this->cache->getCacheItem($this->getCacheKey($slave));
301
            if (is_array($data)) {
302
                if ($data['runnning'] === false || $data['delay'] > $this->maxSlaveDelay) {
303
                    return false;
304
                }
305
            }
306
        }
307
        return true;
308
    }
309
}
310