Failed Conditions
Pull Request — develop (#3368)
by Benjamin
42:38 queued 39:25
created

MysqliConnection::getSequenceNumber()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 3
cp 0.6667
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1.037
1
<?php
2
3
namespace Doctrine\DBAL\Driver\Mysqli;
4
5
use Doctrine\DBAL\Driver\Connection;
6
use Doctrine\DBAL\Driver\DriverException;
7
use Doctrine\DBAL\Driver\PingableConnection;
8
use Doctrine\DBAL\Driver\ResultStatement;
9
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
10
use Doctrine\DBAL\Driver\Statement as DriverStatement;
11
use mysqli;
12
use const MYSQLI_INIT_COMMAND;
13
use const MYSQLI_OPT_CONNECT_TIMEOUT;
14
use const MYSQLI_OPT_LOCAL_INFILE;
15
use const MYSQLI_READ_DEFAULT_FILE;
16
use const MYSQLI_READ_DEFAULT_GROUP;
17
use const MYSQLI_SERVER_PUBLIC_KEY;
18
use function defined;
19
use function floor;
20
use function in_array;
21
use function ini_get;
22
use function mysqli_errno;
23
use function mysqli_error;
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 mixed[] $params
43
     * @param string  $username
44
     * @param string  $password
45
     * @param mixed[] $driverOptions
46
     *
47
     * @throws DriverException
48
     */
49 264
    public function __construct(array $params, $username, $password, array $driverOptions = [])
50
    {
51 264
        $port = $params['port'] ?? ini_get('mysqli.default_port');
52
53
        // Fallback to default MySQL port if not given.
54 264
        if (! $port) {
55
            $port = 3306;
56
        }
57
58 264
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
59 264
        $dbname = $params['dbname'] ?? null;
60
61 264
        $flags = $driverOptions[static::OPTION_FLAGS] ?? null;
62
63 264
        $this->conn = mysqli_init();
64
65 264
        $this->setSecureConnection($params);
66 264
        $this->setDriverOptions($driverOptions);
67
68
        set_error_handler(static function () {
69 258
        });
70
        try {
71 258
            if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) {
0 ignored issues
show
Bug introduced by
It seems like $port can also be of type string; however, parameter $port of mysqli::real_connect() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

71
            if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, /** @scrutinizer ignore-type */ $port, $socket, $flags)) {
Loading history...
72 36
                throw new DriverException($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno);
73
            }
74 228
        } finally {
75 258
            restore_error_handler();
76
        }
77
78 228
        if (! isset($params['charset'])) {
79 222
            return;
80
        }
81
82 6
        $this->conn->set_charset($params['charset']);
83 6
    }
84
85
    /**
86
     * Retrieves mysqli native resource handle.
87
     *
88
     * Could be used if part of your application is not using DBAL.
89
     *
90
     * @return mysqli
91
     */
92
    public function getWrappedResourceHandle()
93
    {
94
        return $this->conn;
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     *
100
     * The server version detection includes a special case for MariaDB
101
     * to support '5.5.5-' prefixed versions introduced in Maria 10+
102
     *
103
     * @link https://jira.mariadb.org/browse/MDEV-4088
104
     */
105 150
    public function getServerVersion()
106
    {
107 150
        $serverInfos = $this->conn->get_server_info();
108 150
        if (stripos($serverInfos, 'mariadb') !== false) {
109 100
            return $serverInfos;
110
        }
111
112 50
        $majorVersion = floor($this->conn->server_version / 10000);
113 50
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
114 50
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
115
116 50
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 156
    public function requiresQueryForServerVersion()
123
    {
124 156
        return false;
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130 1569
    public function prepare(string $sql) : DriverStatement
131
    {
132 1569
        return new MysqliStatement($this->conn, $sql);
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 1125
    public function query(string $sql) : ResultStatement
139
    {
140 1125
        $stmt = $this->prepare($sql);
141 1107
        $stmt->execute();
142
143 1107
        return $stmt;
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149 18
    public function quote(string $input) : string
150
    {
151 18
        return "'" . $this->conn->escape_string($input) . "'";
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 1107
    public function exec(string $statement) : int
158
    {
159 1107
        if ($this->conn->query($statement) === false) {
160 711
            throw new DriverException($this->conn->error, $this->conn->sqlstate, $this->conn->errno);
161
        }
162
163 932
        return $this->conn->affected_rows;
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169 18
    public function lastInsertId() : string
170
    {
171 18
        $insertId = $this->conn->insert_id;
172
173 18
        if ($insertId === 0) {
174 6
            throw DriverException::noInsertId();
175
        }
176
177 12
        return (string) $insertId;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183 6
    public function getSequenceNumber(string $name) : string
184
    {
185 6
        throw new DriverException('MySQL does not support sequences.');
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 96
    public function beginTransaction() : void
192
    {
193 96
        $this->conn->query('START TRANSACTION');
194 96
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 42
    public function commit() : void
200
    {
201 42
        $this->conn->commit();
202 42
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 60
    public function rollBack() : void
208
    {
209 60
        $this->conn->rollback();
210 60
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function errorCode()
216
    {
217
        return $this->conn->errno;
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function errorInfo()
224
    {
225
        return $this->conn->error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->error returns the type string which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Connection::errorInfo() of array<mixed,mixed>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
226
    }
227
228
    /**
229
     * Apply the driver options to the connection.
230
     *
231
     * @param mixed[] $driverOptions
232
     *
233
     * @throws DriverException When one of of the options is not supported.
234
     * @throws DriverException When applying doesn't work - e.g. due to incorrect value.
235
     */
236 264
    private function setDriverOptions(array $driverOptions = [])
237
    {
238
        $supportedDriverOptions = [
239 264
            MYSQLI_OPT_CONNECT_TIMEOUT,
240
            MYSQLI_OPT_LOCAL_INFILE,
241
            MYSQLI_INIT_COMMAND,
242
            MYSQLI_READ_DEFAULT_FILE,
243
            MYSQLI_READ_DEFAULT_GROUP,
244
        ];
245
246 264
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
247 264
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
248
        }
249
250 264
        $exceptionMsg = "%s option '%s' with value '%s'";
251
252 264
        foreach ($driverOptions as $option => $value) {
253 12
            if ($option === static::OPTION_FLAGS) {
254
                continue;
255
            }
256
257 12
            if (! in_array($option, $supportedDriverOptions, true)) {
258 6
                throw new DriverException(
259 6
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
260
                );
261
            }
262
263 6
            if (@mysqli_options($this->conn, $option, $value)) {
264 6
                continue;
265
            }
266
267
            $msg  = sprintf($exceptionMsg, 'Failed to set', $option, $value);
268
            $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
269
270
            throw new DriverException(
271
                $msg,
272
                $this->conn->sqlstate,
273
                $this->conn->errno
274
            );
275
        }
276 258
    }
277
278
    /**
279
     * Pings the server and re-connects when `mysqli.reconnect = 1`
280
     *
281
     * @return bool
282
     */
283 12
    public function ping()
284
    {
285 12
        return $this->conn->ping();
286
    }
287
288
    /**
289
     * Establish a secure connection
290
     *
291
     * @param mixed[] $params
292
     *
293
     * @throws DriverException
294
     */
295 264
    private function setSecureConnection(array $params)
296
    {
297 264
        if (! isset($params['ssl_key']) &&
298 264
            ! isset($params['ssl_cert']) &&
299 264
            ! isset($params['ssl_ca']) &&
300 264
            ! isset($params['ssl_capath']) &&
301 264
            ! isset($params['ssl_cipher'])
302
        ) {
303 264
            return;
304
        }
305
306
        $this->conn->ssl_set(
307
            $params['ssl_key']    ?? null,
308
            $params['ssl_cert']   ?? null,
309
            $params['ssl_ca']     ?? null,
310
            $params['ssl_capath'] ?? null,
311
            $params['ssl_cipher'] ?? null
312
        );
313
    }
314
}
315