Completed
Push — master ( 9b88bf...94cec7 )
by Marco
31s queued 15s
created

Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php (1 issue)

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