Completed
Pull Request — develop (#3515)
by Sergei
20:18
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 2163
    public function __construct(array $params, $username, $password, array $driverOptions = [])
49
    {
50 2163
        $port = $params['port'] ?? (int) ini_get('mysqli.default_port');
51
52
        // Fallback to default MySQL port if not given.
53 2163
        if (! $port) {
54
            $port = 3306;
55
        }
56
57 2163
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
58 2163
        $dbname = $params['dbname'] ?? '';
59 2163
        $host   = $params['host'];
60
61 2163
        if (! empty($params['persistent'])) {
62 2002
            $host = 'p:' . $host;
63
        }
64
65 2163
        $flags = $driverOptions[static::OPTION_FLAGS] ?? 0;
66
67 2163
        $this->conn = mysqli_init();
68
69 2163
        $this->setSecureConnection($params);
70 2163
        $this->setDriverOptions($driverOptions);
71
72
        set_error_handler(static function () {
73 2163
        });
74
        try {
75 2163
            if (! $this->conn->real_connect($host, $username, $password, $dbname, $port, $socket, $flags)) {
76 2156
                throw MysqliException::fromConnectionError($this->conn);
77
            }
78 2156
        } finally {
79 2163
            restore_error_handler();
80
        }
81
82 2156
        if (! isset($params['charset'])) {
83 2156
            return;
84
        }
85
86 1204
        $this->conn->set_charset($params['charset']);
87 1204
    }
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 2156
    public function getServerVersion()
110
    {
111 2156
        $serverInfos = $this->conn->get_server_info();
112 2156
        if (stripos($serverInfos, 'mariadb') !== false) {
113 1229
            return $serverInfos;
114
        }
115
116 927
        $majorVersion = floor($this->conn->server_version / 10000);
117 927
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
118 927
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
119
120 927
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 2170
    public function requiresQueryForServerVersion()
127
    {
128 2170
        return false;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 2142
    public function prepare(string $sql) : DriverStatement
135
    {
136 2142
        return new MysqliStatement($this->conn, $sql);
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 2135
    public function query(string $sql) : ResultStatement
143
    {
144 2135
        $stmt = $this->prepare($sql);
145 2135
        $stmt->execute();
146
147 2135
        return $stmt;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 2030
    public function quote(string $input) : string
154
    {
155 2030
        return "'" . $this->conn->escape_string($input) . "'";
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 2156
    public function exec(string $statement) : int
162
    {
163 2156
        if ($this->conn->query($statement) === false) {
164 2142
            throw MysqliException::fromConnectionError($this->conn);
165
        }
166
167 2156
        return $this->conn->affected_rows;
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173 49
    public function lastInsertId($name = null)
174
    {
175 49
        return $this->conn->insert_id;
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181 2093
    public function beginTransaction() : void
182
    {
183 2093
        $this->conn->query('START TRANSACTION');
184 2093
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 2079
    public function commit() : void
190
    {
191 2079
        if (! $this->conn->commit()) {
192
            throw MysqliException::fromConnectionError($this->conn);
193
        }
194 2079
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 2093
    public function rollBack() : void
200
    {
201 2093
        if (! $this->conn->rollback()) {
202
            throw MysqliException::fromConnectionError($this->conn);
203
        }
204 2093
    }
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 2163
    private function setDriverOptions(array $driverOptions = [])
215
    {
216
        $supportedDriverOptions = [
217 2163
            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 2163
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
225 2163
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
226
        }
227
228 2163
        $exceptionMsg = "%s option '%s' with value '%s'";
229
230 2163
        foreach ($driverOptions as $option => $value) {
231 1358
            if ($option === static::OPTION_FLAGS) {
232
                continue;
233
            }
234
235 1358
            if (! in_array($option, $supportedDriverOptions, true)) {
236 1351
                throw new MysqliException(
237 1351
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
238
                );
239
            }
240
241 1358
            if (@mysqli_options($this->conn, $option, $value)) {
242 1358
                continue;
243
            }
244
245
            throw MysqliException::fromConnectionError($this->conn);
246
        }
247 2163
    }
248
249
    /**
250
     * Pings the server and re-connects when `mysqli.reconnect = 1`
251
     *
252
     * @return bool
253
     */
254 2023
    public function ping()
255
    {
256 2023
        return $this->conn->ping();
257
    }
258
259
    /**
260
     * Establish a secure connection
261
     *
262
     * @param mixed[] $params
263
     *
264
     * @throws MysqliException
265
     */
266 2163
    private function setSecureConnection(array $params)
267
    {
268 2163
        if (! isset($params['ssl_key']) &&
269 2163
            ! isset($params['ssl_cert']) &&
270 2163
            ! isset($params['ssl_ca']) &&
271 2163
            ! isset($params['ssl_capath']) &&
272 2163
            ! isset($params['ssl_cipher'])
273
        ) {
274 2163
            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