Failed Conditions
Push — develop ( 55bd22...e46c09 )
by Sergei
33s queued 23s
created

MysqliConnection::__construct()   A

Complexity

Conditions 5
Paths 24

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5.583

Importance

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