Failed Conditions
Pull Request — develop (#3335)
by
unknown
12:27
created

MysqliConnection::quote()   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\PingableConnection;
7
use Doctrine\DBAL\Driver\ResultStatement;
8
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
9
use Doctrine\DBAL\Driver\Statement as DriverStatement;
10
use mysqli;
11
use const MYSQLI_INIT_COMMAND;
12
use const MYSQLI_OPT_CONNECT_TIMEOUT;
13
use const MYSQLI_OPT_LOCAL_INFILE;
14
use const MYSQLI_READ_DEFAULT_FILE;
15
use const MYSQLI_READ_DEFAULT_GROUP;
16
use const MYSQLI_SERVER_PUBLIC_KEY;
17
use function defined;
18
use function floor;
19
use function in_array;
20
use function ini_get;
21
use function mysqli_errno;
22
use function mysqli_error;
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 258
    public function __construct(array $params, $username, $password, array $driverOptions = [])
49
    {
50 258
        $port = $params['port'] ?? ini_get('mysqli.default_port');
51
52
        // Fallback to default MySQL port if not given.
53 258
        if (! $port) {
54
            $port = 3306;
55
        }
56
57 258
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
58 258
        $dbname = $params['dbname'] ?? null;
59
60 258
        $flags = $driverOptions[static::OPTION_FLAGS] ?? null;
61
62 258
        $this->conn = mysqli_init();
63
64 258
        $this->setSecureConnection($params);
65 258
        $this->setDriverOptions($driverOptions);
66
67
        set_error_handler(static function () {
68 252
        });
69
        try {
70 252
            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

70
            if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, /** @scrutinizer ignore-type */ $port, $socket, $flags)) {
Loading history...
71 36
                throw new MysqliException($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno);
72
            }
73 222
        } finally {
74 252
            restore_error_handler();
75
        }
76
77 222
        if (! isset($params['charset'])) {
78 216
            return;
79
        }
80
81 6
        $this->conn->set_charset($params['charset']);
82 6
    }
83
84
    /**
85
     * Retrieves mysqli native resource handle.
86
     *
87
     * Could be used if part of your application is not using DBAL.
88
     *
89
     * @return mysqli
90
     */
91
    public function getWrappedResourceHandle()
92
    {
93
        return $this->conn;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * The server version detection includes a special case for MariaDB
100
     * to support '5.5.5-' prefixed versions introduced in Maria 10+
101
     *
102
     * @link https://jira.mariadb.org/browse/MDEV-4088
103
     */
104 144
    public function getServerVersion()
105
    {
106 144
        $serverInfos = $this->conn->get_server_info();
107 144
        if (stripos($serverInfos, 'mariadb') !== false) {
108 96
            return $serverInfos;
109
        }
110
111 48
        $majorVersion = floor($this->conn->server_version / 10000);
112 48
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
113 48
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
114
115 48
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 150
    public function requiresQueryForServerVersion()
122
    {
123 150
        return false;
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129 1569
    public function prepare(string $sql) : DriverStatement
130
    {
131 1569
        return new MysqliStatement($this->conn, $sql);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 1125
    public function query(string $sql) : ResultStatement
138
    {
139 1125
        $stmt = $this->prepare($sql);
140 1107
        $stmt->execute();
141
142 1107
        return $stmt;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 18
    public function quote(string $input) : string
149
    {
150 18
        return "'" . $this->conn->escape_string($input) . "'";
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 1095
    public function exec(string $statement) : int
157
    {
158 1095
        if ($this->conn->query($statement) === false) {
159 699
            throw new MysqliException($this->conn->error, $this->conn->sqlstate, $this->conn->errno);
160
        }
161
162 920
        return $this->conn->affected_rows;
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 12
    public function lastInsertId(?string $name = null) : string
169
    {
170 12
        return (string) $this->conn->insert_id;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 96
    public function beginTransaction() : void
177
    {
178 96
        $this->conn->query('START TRANSACTION');
179 96
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 42
    public function commit() : void
185
    {
186 42
        $this->conn->commit();
187 42
    }
188
189
    /**
190
     * {@inheritdoc}non-PHPdoc)
191
     */
192 60
    public function rollBack() : void
193
    {
194 60
        $this->conn->rollback();
195 60
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function errorCode()
201
    {
202
        return $this->conn->errno;
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    public function errorInfo()
209
    {
210
        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...
211
    }
212
213
    /**
214
     * Apply the driver options to the connection.
215
     *
216
     * @param mixed[] $driverOptions
217
     *
218
     * @throws MysqliException When one of of the options is not supported.
219
     * @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
220
     */
221 258
    private function setDriverOptions(array $driverOptions = [])
222
    {
223
        $supportedDriverOptions = [
224 258
            MYSQLI_OPT_CONNECT_TIMEOUT,
225
            MYSQLI_OPT_LOCAL_INFILE,
226
            MYSQLI_INIT_COMMAND,
227
            MYSQLI_READ_DEFAULT_FILE,
228
            MYSQLI_READ_DEFAULT_GROUP,
229
        ];
230
231 258
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
232 258
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
233
        }
234
235 258
        $exceptionMsg = "%s option '%s' with value '%s'";
236
237 258
        foreach ($driverOptions as $option => $value) {
238 12
            if ($option === static::OPTION_FLAGS) {
239
                continue;
240
            }
241
242 12
            if (! in_array($option, $supportedDriverOptions, true)) {
243 6
                throw new MysqliException(
244 6
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
245
                );
246
            }
247
248 6
            if (@mysqli_options($this->conn, $option, $value)) {
249 6
                continue;
250
            }
251
252
            $msg  = sprintf($exceptionMsg, 'Failed to set', $option, $value);
253
            $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
254
255
            throw new MysqliException(
256
                $msg,
257
                $this->conn->sqlstate,
258
                $this->conn->errno
259
            );
260
        }
261 252
    }
262
263
    /**
264
     * Pings the server and re-connects when `mysqli.reconnect = 1`
265
     *
266
     * @return bool
267
     */
268 12
    public function ping()
269
    {
270 12
        return $this->conn->ping();
271
    }
272
273
    /**
274
     * Establish a secure connection
275
     *
276
     * @param mixed[] $params
277
     *
278
     * @throws MysqliException
279
     */
280 258
    private function setSecureConnection(array $params)
281
    {
282 258
        if (! isset($params['ssl_key']) &&
283 258
            ! isset($params['ssl_cert']) &&
284 258
            ! isset($params['ssl_ca']) &&
285 258
            ! isset($params['ssl_capath']) &&
286 258
            ! isset($params['ssl_cipher'])
287
        ) {
288 258
            return;
289
        }
290
291
        $this->conn->ssl_set(
292
            $params['ssl_key']    ?? null,
293
            $params['ssl_cert']   ?? null,
294
            $params['ssl_ca']     ?? null,
295
            $params['ssl_capath'] ?? null,
296
            $params['ssl_cipher'] ?? null
297
        );
298
    }
299
}
300