Failed Conditions
Pull Request — develop (#3367)
by Benjamin
10:59
created

MysqliConnection::commit()   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
dl 0
loc 3
ccs 2
cts 3
cp 0.6667
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
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 24
    public function lastInsertId(?string $name = null) : string
170
    {
171 24
        $insertId = $this->conn->insert_id;
172
173 24
        if ($insertId === 0) {
174 12
            throw DriverException::noInsertId();
175
        }
176
177 12
        return (string) $insertId;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183 96
    public function beginTransaction() : void
184
    {
185 96
        $this->conn->query('START TRANSACTION');
186 96
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 42
    public function commit() : void
192
    {
193 42
        $this->conn->commit();
194 42
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 60
    public function rollBack() : void
200
    {
201 60
        $this->conn->rollback();
202 60
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function errorCode()
208
    {
209
        return $this->conn->errno;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function errorInfo()
216
    {
217
        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...
218
    }
219
220
    /**
221
     * Apply the driver options to the connection.
222
     *
223
     * @param mixed[] $driverOptions
224
     *
225
     * @throws DriverException When one of of the options is not supported.
226
     * @throws DriverException When applying doesn't work - e.g. due to incorrect value.
227
     */
228 264
    private function setDriverOptions(array $driverOptions = [])
229
    {
230
        $supportedDriverOptions = [
231 264
            MYSQLI_OPT_CONNECT_TIMEOUT,
232
            MYSQLI_OPT_LOCAL_INFILE,
233
            MYSQLI_INIT_COMMAND,
234
            MYSQLI_READ_DEFAULT_FILE,
235
            MYSQLI_READ_DEFAULT_GROUP,
236
        ];
237
238 264
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
239 264
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
240
        }
241
242 264
        $exceptionMsg = "%s option '%s' with value '%s'";
243
244 264
        foreach ($driverOptions as $option => $value) {
245 12
            if ($option === static::OPTION_FLAGS) {
246
                continue;
247
            }
248
249 12
            if (! in_array($option, $supportedDriverOptions, true)) {
250 6
                throw new DriverException(
251 6
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
252
                );
253
            }
254
255 6
            if (@mysqli_options($this->conn, $option, $value)) {
256 6
                continue;
257
            }
258
259
            $msg  = sprintf($exceptionMsg, 'Failed to set', $option, $value);
260
            $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
261
262
            throw new DriverException(
263
                $msg,
264
                $this->conn->sqlstate,
265
                $this->conn->errno
266
            );
267
        }
268 258
    }
269
270
    /**
271
     * Pings the server and re-connects when `mysqli.reconnect = 1`
272
     *
273
     * @return bool
274
     */
275 12
    public function ping()
276
    {
277 12
        return $this->conn->ping();
278
    }
279
280
    /**
281
     * Establish a secure connection
282
     *
283
     * @param mixed[] $params
284
     *
285
     * @throws DriverException
286
     */
287 264
    private function setSecureConnection(array $params)
288
    {
289 264
        if (! isset($params['ssl_key']) &&
290 264
            ! isset($params['ssl_cert']) &&
291 264
            ! isset($params['ssl_ca']) &&
292 264
            ! isset($params['ssl_capath']) &&
293 264
            ! isset($params['ssl_cipher'])
294
        ) {
295 264
            return;
296
        }
297
298
        $this->conn->ssl_set(
299
            $params['ssl_key']    ?? null,
300
            $params['ssl_cert']   ?? null,
301
            $params['ssl_ca']     ?? null,
302
            $params['ssl_capath'] ?? null,
303
            $params['ssl_cipher'] ?? null
304
        );
305
    }
306
}
307