Failed Conditions
Pull Request — develop (#3525)
by Jonathan
12:46
created

MysqliConnection   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Test Coverage

Coverage 55.47%

Importance

Changes 0
Metric Value
wmc 33
eloc 77
dl 0
loc 253
ccs 76
cts 137
cp 0.5547
rs 9.76
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A requiresQueryForServerVersion() 0 3 1
A setDriverOptions() 0 32 6
A rollBack() 0 4 2
A getWrappedResourceHandle() 0 3 1
A exec() 0 7 2
A prepare() 0 3 1
A query() 0 6 1
A ping() 0 3 1
A setSecureConnection() 0 17 6
A lastInsertId() 0 3 1
A getServerVersion() 0 12 2
A quote() 0 3 1
A beginTransaction() 0 3 1
A __construct() 0 39 5
A commit() 0 4 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver\Mysqli;
6
7
use Doctrine\DBAL\Driver\Connection;
8
use Doctrine\DBAL\Driver\PingableConnection;
9
use Doctrine\DBAL\Driver\ResultStatement;
10
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
11
use Doctrine\DBAL\Driver\Statement as DriverStatement;
12
use mysqli;
13
use const MYSQLI_INIT_COMMAND;
14
use const MYSQLI_OPT_CONNECT_TIMEOUT;
15
use const MYSQLI_OPT_LOCAL_INFILE;
16
use const MYSQLI_READ_DEFAULT_FILE;
17
use const MYSQLI_READ_DEFAULT_GROUP;
18
use const MYSQLI_SERVER_PUBLIC_KEY;
19
use function defined;
20
use function floor;
21
use function in_array;
22
use function ini_get;
23
use function mysqli_init;
24
use function mysqli_options;
25
use function restore_error_handler;
26
use function set_error_handler;
27
use function sprintf;
28
use function stripos;
29
30
class MysqliConnection implements Connection, PingableConnection, ServerInfoAwareConnection
31
{
32
    /**
33
     * Name of the option to set connection flags
34
     */
35
    public const OPTION_FLAGS = 'flags';
36
37
    /** @var mysqli */
38
    private $conn;
39
40
    /**
41
     * @param mixed[] $params
42
     * @param string  $username
43
     * @param string  $password
44
     * @param mixed[] $driverOptions
45
     *
46
     * @throws MysqliException
47
     */
48 2782
    public function __construct(array $params, $username, $password, array $driverOptions = [])
49
    {
50 2782
        $port = $params['port'] ?? (int) ini_get('mysqli.default_port');
51
52
        // Fallback to default MySQL port if not given.
53 2782
        if (! $port) {
54
            $port = 3306;
55
        }
56
57 2782
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
58 2782
        $dbname = $params['dbname'] ?? '';
59 2782
        $host   = $params['host'];
60
61 2782
        if (! empty($params['persistent'])) {
62 2574
            $host = 'p:' . $host;
63
        }
64
65 2782
        $flags = $driverOptions[static::OPTION_FLAGS] ?? 0;
66
67 2782
        $this->conn = mysqli_init();
68
69 2782
        $this->setSecureConnection($params);
70 2782
        $this->setDriverOptions($driverOptions);
71
72
        set_error_handler(static function () {
73 2782
        });
74
        try {
75 2782
            if (! $this->conn->real_connect($host, $username, $password, $dbname, $port, $socket, $flags)) {
76 2773
                throw MysqliException::fromConnectionError($this->conn);
77
            }
78 2772
        } finally {
79 2782
            restore_error_handler();
80
        }
81
82 2772
        if (! isset($params['charset'])) {
83 2772
            return;
84
        }
85
86 1548
        $this->conn->set_charset($params['charset']);
87 1548
    }
88
89
    /**
90
     * Retrieves mysqli native resource handle.
91
     *
92
     * Could be used if part of your application is not using DBAL.
93
     *
94
     * @return mysqli
95
     */
96
    public function getWrappedResourceHandle()
97
    {
98
        return $this->conn;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     *
104
     * The server version detection includes a special case for MariaDB
105
     * to support '5.5.5-' prefixed versions introduced in Maria 10+
106
     *
107
     * @link https://jira.mariadb.org/browse/MDEV-4088
108
     */
109 2772
    public function getServerVersion()
110
    {
111 2772
        $serverInfos = $this->conn->get_server_info();
112 2772
        if (stripos($serverInfos, 'mariadb') !== false) {
113 919
            return $serverInfos;
114
        }
115
116 1853
        $majorVersion = floor($this->conn->server_version / 10000);
117 1853
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
118 1853
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
119
120 1853
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 2792
    public function requiresQueryForServerVersion()
127
    {
128 2792
        return false;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 2754
    public function prepare(string $sql) : DriverStatement
135
    {
136 2754
        return new MysqliStatement($this->conn, $sql);
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 2745
    public function query(string $sql) : ResultStatement
143
    {
144 2745
        $stmt = $this->prepare($sql);
145 2745
        $stmt->execute();
146
147 2745
        return $stmt;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 2610
    public function quote(string $input) : string
154
    {
155 2610
        return "'" . $this->conn->escape_string($input) . "'";
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 2772
    public function exec(string $statement) : int
162
    {
163 2772
        if ($this->conn->query($statement) === false) {
164 2754
            throw MysqliException::fromConnectionError($this->conn);
165
        }
166
167 2772
        return $this->conn->affected_rows;
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173 63
    public function lastInsertId($name = null)
174
    {
175 63
        return $this->conn->insert_id;
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181 2691
    public function beginTransaction() : void
182
    {
183 2691
        $this->conn->query('START TRANSACTION');
184 2691
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 2673
    public function commit() : void
190
    {
191 2673
        if (! $this->conn->commit()) {
192
            throw MysqliException::fromConnectionError($this->conn);
193
        }
194 2673
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 2691
    public function rollBack() : void
200
    {
201 2691
        if (! $this->conn->rollback()) {
202
            throw MysqliException::fromConnectionError($this->conn);
203
        }
204 2691
    }
205
206
    /**
207
     * Apply the driver options to the connection.
208
     *
209
     * @param mixed[] $driverOptions
210
     *
211
     * @throws MysqliException When one of of the options is not supported.
212
     * @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
213
     */
214 2782
    private function setDriverOptions(array $driverOptions = [])
215
    {
216
        $supportedDriverOptions = [
217 2782
            MYSQLI_OPT_CONNECT_TIMEOUT,
218
            MYSQLI_OPT_LOCAL_INFILE,
219
            MYSQLI_INIT_COMMAND,
220
            MYSQLI_READ_DEFAULT_FILE,
221
            MYSQLI_READ_DEFAULT_GROUP,
222
        ];
223
224 2782
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
225 2782
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
226
        }
227
228 2782
        $exceptionMsg = "%s option '%s' with value '%s'";
229
230 2782
        foreach ($driverOptions as $option => $value) {
231 1746
            if ($option === static::OPTION_FLAGS) {
232
                continue;
233
            }
234
235 1746
            if (! in_array($option, $supportedDriverOptions, true)) {
236 1737
                throw new MysqliException(
237 1737
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
238
                );
239
            }
240
241 1746
            if (@mysqli_options($this->conn, $option, $value)) {
242 1746
                continue;
243
            }
244
245
            throw MysqliException::fromConnectionError($this->conn);
246
        }
247 2782
    }
248
249
    /**
250
     * Pings the server and re-connects when `mysqli.reconnect = 1`
251
     *
252
     * @return bool
253
     */
254 2601
    public function ping()
255
    {
256 2601
        return $this->conn->ping();
257
    }
258
259
    /**
260
     * Establish a secure connection
261
     *
262
     * @param mixed[] $params
263
     *
264
     * @throws MysqliException
265
     */
266 2782
    private function setSecureConnection(array $params)
267
    {
268 2782
        if (! isset($params['ssl_key']) &&
269 2782
            ! isset($params['ssl_cert']) &&
270 2782
            ! isset($params['ssl_ca']) &&
271 2782
            ! isset($params['ssl_capath']) &&
272 2782
            ! isset($params['ssl_cipher'])
273
        ) {
274 2782
            return;
275
        }
276
277
        $this->conn->ssl_set(
278
            $params['ssl_key']    ?? null,
279
            $params['ssl_cert']   ?? null,
280
            $params['ssl_ca']     ?? null,
281
            $params['ssl_capath'] ?? null,
282
            $params['ssl_cipher'] ?? null
283
        );
284
    }
285
}
286