Passed
Push — drop-deprecated ( db0b1f )
by Michael
27:00
created

MysqliConnection::setDriverOptions()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 11.6215

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 32
ccs 12
cts 26
cp 0.4615
rs 9.0444
c 0
b 0
f 0
cc 6
nc 10
nop 1
crap 11.6215
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 2147
    public function __construct(array $params, $username, $password, array $driverOptions = [])
49
    {
50 2147
        $port = $params['port'] ?? (int) ini_get('mysqli.default_port');
51
52
        // Fallback to default MySQL port if not given.
53 2147
        if (! $port) {
54
            $port = 3306;
55
        }
56
57 2147
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
58 2147
        $dbname = $params['dbname'] ?? '';
59
60 2147
        $flags = $driverOptions[static::OPTION_FLAGS] ?? 0;
61
62 2147
        $this->conn = mysqli_init();
63
64 2147
        $this->setSecureConnection($params);
65 2147
        $this->setDriverOptions($driverOptions);
66
67
        set_error_handler(static function () {
68 2147
        });
69
        try {
70 2147
            if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) {
71 2140
                throw MysqliException::fromConnectionError($this->conn);
72
            }
73 2140
        } finally {
74 2147
            restore_error_handler();
75
        }
76
77 2140
        if (! isset($params['charset'])) {
78 2140
            return;
79
        }
80
81 1181
        $this->conn->set_charset($params['charset']);
82 1181
    }
83
84
    /**
85
     * Retrieves mysqli native resource handle.
86
     *
87
     * Could be used if part of your application is not using DBAL.
88
     *
89
     * @return mysqli
90
     */
91
    public function getWrappedResourceHandle()
92
    {
93
        return $this->conn;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * The server version detection includes a special case for MariaDB
100
     * to support '5.5.5-' prefixed versions introduced in Maria 10+
101
     *
102
     * @link https://jira.mariadb.org/browse/MDEV-4088
103
     */
104 2140
    public function getServerVersion()
105
    {
106 2140
        $serverInfos = $this->conn->get_server_info();
107 2140
        if (stripos($serverInfos, 'mariadb') !== false) {
108 1223
            return $serverInfos;
109
        }
110
111 917
        $majorVersion = floor($this->conn->server_version / 10000);
112 917
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
113 917
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
114
115 917
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 2154
    public function requiresQueryForServerVersion()
122
    {
123 2154
        return false;
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129 2126
    public function prepare(string $sql) : DriverStatement
130
    {
131 2126
        return new MysqliStatement($this->conn, $sql);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 2119
    public function query(string $sql) : ResultStatement
138
    {
139 2119
        $stmt = $this->prepare($sql);
140 2119
        $stmt->execute();
141
142 2119
        return $stmt;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 2014
    public function quote(string $input) : string
149
    {
150 2014
        return "'" . $this->conn->escape_string($input) . "'";
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 2140
    public function exec(string $statement) : int
157
    {
158 2140
        if ($this->conn->query($statement) === false) {
159 2126
            throw MysqliException::fromConnectionError($this->conn);
160
        }
161
162 2140
        return $this->conn->affected_rows;
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 49
    public function lastInsertId($name = null)
169
    {
170 49
        return $this->conn->insert_id;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 2077
    public function beginTransaction() : void
177
    {
178 2077
        $this->conn->query('START TRANSACTION');
179 2077
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 2063
    public function commit() : void
185
    {
186 2063
        if (! $this->conn->commit()) {
187
            throw MysqliException::fromConnectionError($this->conn);
188
        }
189 2063
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194 2077
    public function rollBack() : void
195
    {
196 2077
        if (! $this->conn->rollback()) {
197
            throw MysqliException::fromConnectionError($this->conn);
198
        }
199 2077
    }
200
201
    /**
202
     * Apply the driver options to the connection.
203
     *
204
     * @param mixed[] $driverOptions
205
     *
206
     * @throws MysqliException When one of of the options is not supported.
207
     * @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
208
     */
209 2147
    private function setDriverOptions(array $driverOptions = [])
210
    {
211
        $supportedDriverOptions = [
212 2147
            MYSQLI_OPT_CONNECT_TIMEOUT,
213
            MYSQLI_OPT_LOCAL_INFILE,
214
            MYSQLI_INIT_COMMAND,
215
            MYSQLI_READ_DEFAULT_FILE,
216
            MYSQLI_READ_DEFAULT_GROUP,
217
        ];
218
219 2147
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
220 2147
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
221
        }
222
223 2147
        $exceptionMsg = "%s option '%s' with value '%s'";
224
225 2147
        foreach ($driverOptions as $option => $value) {
226 1342
            if ($option === static::OPTION_FLAGS) {
227
                continue;
228
            }
229
230 1342
            if (! in_array($option, $supportedDriverOptions, true)) {
231 1335
                throw new MysqliException(
232 1335
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
233
                );
234
            }
235
236 1342
            if (@mysqli_options($this->conn, $option, $value)) {
237 1342
                continue;
238
            }
239
240
            throw MysqliException::fromConnectionError($this->conn);
241
        }
242 2147
    }
243
244
    /**
245
     * Pings the server and re-connects when `mysqli.reconnect = 1`
246
     *
247
     * @return bool
248
     */
249 2007
    public function ping()
250
    {
251 2007
        return $this->conn->ping();
252
    }
253
254
    /**
255
     * Establish a secure connection
256
     *
257
     * @param mixed[] $params
258
     *
259
     * @throws MysqliException
260
     */
261 2147
    private function setSecureConnection(array $params)
262
    {
263 2147
        if (! isset($params['ssl_key']) &&
264 2147
            ! isset($params['ssl_cert']) &&
265 2147
            ! isset($params['ssl_ca']) &&
266 2147
            ! isset($params['ssl_capath']) &&
267 2147
            ! isset($params['ssl_cipher'])
268
        ) {
269 2147
            return;
270
        }
271
272
        $this->conn->ssl_set(
273
            $params['ssl_key']    ?? null,
274
            $params['ssl_cert']   ?? null,
275
            $params['ssl_ca']     ?? null,
276
            $params['ssl_capath'] ?? null,
277
            $params['ssl_cipher'] ?? null
278
        );
279
    }
280
}
281