Failed Conditions
Push — master ( 01c22b...e42c1f )
by Marco
79:13 queued 10s
created

MysqliConnection::setDriverOptions()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 15.8865

Importance

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