Failed Conditions
Pull Request — 3.0.x (#3932)
by Sergei
63:06
created

MysqliConnection::getPort()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Doctrine\DBAL\Driver\Mysqli;
4
5
use Doctrine\DBAL\Driver\PingableConnection;
6
use Doctrine\DBAL\Driver\ResultStatement;
7
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
8
use Doctrine\DBAL\Driver\Statement as DriverStatement;
9
use Doctrine\DBAL\ParameterType;
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 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
    public function __construct(array $params, $username, $password, array $driverOptions = [])
49
    {
50
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
51
        $dbname = $params['dbname'] ?? null;
52
        $host   = $params['host'];
53
        $port   = $this->getPort($params);
54
55
        if (! empty($params['persistent'])) {
56
            $host = 'p:' . $host;
57
        }
58
59
        $flags = $driverOptions[static::OPTION_FLAGS] ?? null;
60
61
        $this->conn = mysqli_init();
62
63
        $this->setSecureConnection($params);
64
        $this->setDriverOptions($driverOptions);
65
66
        set_error_handler(static function () : bool {
67
            return true;
68
        });
69
70
        try {
71
            if (! $this->conn->real_connect($host, $username, $password, $dbname, $port, $socket, $flags)) {
72
                throw new MysqliException($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno);
73
            }
74
        } finally {
75
            restore_error_handler();
76
        }
77
78
        if (! isset($params['charset'])) {
79
            return;
80
        }
81
82
        $this->conn->set_charset($params['charset']);
83
    }
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
    public function getServerVersion()
106
    {
107
        $serverInfos = $this->conn->get_server_info();
108
        if (stripos($serverInfos, 'mariadb') !== false) {
109
            return $serverInfos;
110
        }
111
112
        $majorVersion = floor($this->conn->server_version / 10000);
113
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
114
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
115
116
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function requiresQueryForServerVersion()
123
    {
124
        return false;
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function prepare(string $sql) : DriverStatement
131
    {
132
        return new MysqliStatement($this->conn, $sql);
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function query(string $sql) : ResultStatement
139
    {
140
        $stmt = $this->prepare($sql);
141
        $stmt->execute();
142
143
        return $stmt;
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function quote($input, $type = ParameterType::STRING)
150
    {
151
        return "'" . $this->conn->escape_string($input) . "'";
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function exec(string $statement) : int
158
    {
159
        if ($this->conn->query($statement) === false) {
160
            throw new MysqliException($this->conn->error, $this->conn->sqlstate, $this->conn->errno);
161
        }
162
163
        return $this->conn->affected_rows;
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    public function lastInsertId($name = null)
170
    {
171
        return $this->conn->insert_id;
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function beginTransaction()
178
    {
179
        $this->conn->query('START TRANSACTION');
180
181
        return true;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function commit()
188
    {
189
        return $this->conn->commit();
190
    }
191
192
    /**
193
     * {@inheritdoc}non-PHPdoc)
194
     */
195
    public function rollBack()
196
    {
197
        return $this->conn->rollback();
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function errorCode()
204
    {
205
        return $this->conn->errno;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function errorInfo()
212
    {
213
        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...
214
    }
215
216
    /**
217
     * @param array<string,mixed> $params
218
     */
219
    private function getPort(array $params) : int
220
    {
221
        if (isset($params['port'])) {
222
            return $params['port'];
223
        }
224
225
        $port = ini_get('mysqli.default_port');
226
227
        if ($port !== false) {
228
            return (int) $port;
229
        }
230
231
        // Fallback to default MySQL port if not given.
232
        return 3306;
233
    }
234
235
    /**
236
     * Apply the driver options to the connection.
237
     *
238
     * @param mixed[] $driverOptions
239
     *
240
     * @throws MysqliException When one of of the options is not supported.
241
     * @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
242
     */
243
    private function setDriverOptions(array $driverOptions = []) : void
244
    {
245
        $supportedDriverOptions = [
246
            MYSQLI_OPT_CONNECT_TIMEOUT,
247
            MYSQLI_OPT_LOCAL_INFILE,
248
            MYSQLI_INIT_COMMAND,
249
            MYSQLI_READ_DEFAULT_FILE,
250
            MYSQLI_READ_DEFAULT_GROUP,
251
        ];
252
253
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
254
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
255
        }
256
257
        $exceptionMsg = "%s option '%s' with value '%s'";
258
259
        foreach ($driverOptions as $option => $value) {
260
            if ($option === static::OPTION_FLAGS) {
261
                continue;
262
            }
263
264
            if (! in_array($option, $supportedDriverOptions, true)) {
265
                throw new MysqliException(
266
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
267
                );
268
            }
269
270
            if (@mysqli_options($this->conn, $option, $value)) {
271
                continue;
272
            }
273
274
            $msg  = sprintf($exceptionMsg, 'Failed to set', $option, $value);
275
            $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
276
277
            throw new MysqliException(
278
                $msg,
279
                $this->conn->sqlstate,
280
                $this->conn->errno
281
            );
282
        }
283
    }
284
285
    /**
286
     * Pings the server and re-connects when `mysqli.reconnect = 1`
287
     *
288
     * @return bool
289
     */
290
    public function ping()
291
    {
292
        return $this->conn->ping();
293
    }
294
295
    /**
296
     * Establish a secure connection
297
     *
298
     * @param mixed[] $params
299
     *
300
     * @throws MysqliException
301
     */
302
    private function setSecureConnection(array $params) : void
303
    {
304
        if (! isset($params['ssl_key']) &&
305
            ! isset($params['ssl_cert']) &&
306
            ! isset($params['ssl_ca']) &&
307
            ! isset($params['ssl_capath']) &&
308
            ! isset($params['ssl_cipher'])
309
        ) {
310
            return;
311
        }
312
313
        $this->conn->ssl_set(
314
            $params['ssl_key']    ?? null,
315
            $params['ssl_cert']   ?? null,
316
            $params['ssl_ca']     ?? null,
317
            $params['ssl_capath'] ?? null,
318
            $params['ssl_cipher'] ?? null
319
        );
320
    }
321
}
322