MysqliConnection.php 7.56 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Driver\Mysqli;

5
use Doctrine\DBAL\Driver\Connection;
Steve Müller's avatar
Steve Müller committed
6
use Doctrine\DBAL\Driver\PingableConnection;
7
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
8
use Doctrine\DBAL\ParameterType;
9
use mysqli;
10 11 12 13 14 15 16 17 18 19 20 21 22
use function defined;
use function floor;
use function func_get_args;
use function in_array;
use function ini_get;
use function mysqli_errno;
use function mysqli_error;
use function mysqli_init;
use function mysqli_options;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function stripos;
Grégoire Paris's avatar
Grégoire Paris committed
23 24 25 26 27 28
use const MYSQLI_INIT_COMMAND;
use const MYSQLI_OPT_CONNECT_TIMEOUT;
use const MYSQLI_OPT_LOCAL_INFILE;
use const MYSQLI_READ_DEFAULT_FILE;
use const MYSQLI_READ_DEFAULT_GROUP;
use const MYSQLI_SERVER_PUBLIC_KEY;
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

Benjamin Morel's avatar
Benjamin Morel committed
40
    /**
41 42 43 44
     * @param mixed[] $params
     * @param string  $username
     * @param string  $password
     * @param mixed[] $driverOptions
Benjamin Morel's avatar
Benjamin Morel committed
45
     *
46
     * @throws MysqliException
Benjamin Morel's avatar
Benjamin Morel committed
47
     */
48
    public function __construct(array $params, $username, $password, array $driverOptions = [])
49
    {
50
        $port = $params['port'] ?? ini_get('mysqli.default_port');
51 52

        // Fallback to default MySQL port if not given.
53
        if (! $port) {
54 55 56
            $port = 3306;
        }

57 58
        $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
        $dbname = $params['dbname'] ?? null;
59

60
        $flags = $driverOptions[static::OPTION_FLAGS] ?? null;
61

62
        $this->conn = mysqli_init();
63

64
        $this->setSecureConnection($params);
65 66
        $this->setDriverOptions($driverOptions);

67 68
        set_error_handler(static function () {
        });
69
        try {
70 71
            if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) {
                throw new MysqliException($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno);
72 73
            }
        } finally {
74
            restore_error_handler();
75
        }
76

77 78
        if (! isset($params['charset'])) {
            return;
79
        }
80

81
        $this->conn->set_charset($params['charset']);
82 83
    }

84
    /**
Benjamin Morel's avatar
Benjamin Morel committed
85
     * Retrieves mysqli native resource handle.
86
     *
Benjamin Morel's avatar
Benjamin Morel committed
87
     * Could be used if part of your application is not using DBAL.
88
     *
89
     * @return mysqli
90 91 92
     */
    public function getWrappedResourceHandle()
    {
93
        return $this->conn;
94 95
    }

96 97
    /**
     * {@inheritdoc}
98 99 100
     *
     * The server version detection includes a special case for MariaDB
     * to support '5.5.5-' prefixed versions introduced in Maria 10+
101
     *
102
     * @link https://jira.mariadb.org/browse/MDEV-4088
103 104 105
     */
    public function getServerVersion()
    {
106
        $serverInfos = $this->conn->get_server_info();
107
        if (stripos($serverInfos, 'mariadb') !== false) {
108 109 110
            return $serverInfos;
        }

111 112 113
        $majorVersion = floor($this->conn->server_version / 10000);
        $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
        $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
114 115 116 117 118 119 120 121 122 123 124 125

        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
    }

    /**
     * {@inheritdoc}
     */
    public function requiresQueryForServerVersion()
    {
        return false;
    }

126 127 128
    /**
     * {@inheritdoc}
     */
129 130
    public function prepare($prepareString)
    {
131
        return new MysqliStatement($this->conn, $prepareString);
132 133
    }

134 135 136
    /**
     * {@inheritdoc}
     */
137 138 139
    public function query()
    {
        $args = func_get_args();
140
        $sql  = $args[0];
141 142
        $stmt = $this->prepare($sql);
        $stmt->execute();
143

144 145 146
        return $stmt;
    }

147 148 149
    /**
     * {@inheritdoc}
     */
150
    public function quote($input, $type = ParameterType::STRING)
151
    {
152
        return "'" . $this->conn->escape_string($input) . "'";
153 154
    }

155 156 157
    /**
     * {@inheritdoc}
     */
158 159
    public function exec($statement)
    {
160 161
        if ($this->conn->query($statement) === false) {
            throw new MysqliException($this->conn->error, $this->conn->sqlstate, $this->conn->errno);
162 163
        }

164
        return $this->conn->affected_rows;
165 166
    }

167 168 169
    /**
     * {@inheritdoc}
     */
170 171
    public function lastInsertId($name = null)
    {
172
        return $this->conn->insert_id;
173 174
    }

175 176 177
    /**
     * {@inheritdoc}
     */
178 179
    public function beginTransaction()
    {
180
        $this->conn->query('START TRANSACTION');
181

182 183 184
        return true;
    }

185 186 187
    /**
     * {@inheritdoc}
     */
188 189
    public function commit()
    {
190
        return $this->conn->commit();
191 192
    }

193 194 195
    /**
     * {@inheritdoc}non-PHPdoc)
     */
196 197
    public function rollBack()
    {
198
        return $this->conn->rollback();
199 200
    }

201 202
    /**
     * {@inheritdoc}
203 204
     *
     * @return int
205
     */
206 207
    public function errorCode()
    {
208
        return $this->conn->errno;
209 210
    }

211 212
    /**
     * {@inheritdoc}
213 214
     *
     * @return string
215
     */
216 217
    public function errorInfo()
    {
218
        return $this->conn->error;
219
    }
220 221 222 223

    /**
     * Apply the driver options to the connection.
     *
224
     * @param mixed[] $driverOptions
225 226 227 228
     *
     * @throws MysqliException When one of of the options is not supported.
     * @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
     */
229
    private function setDriverOptions(array $driverOptions = []) : void
230
    {
231
        $supportedDriverOptions = [
232 233 234 235 236
            MYSQLI_OPT_CONNECT_TIMEOUT,
            MYSQLI_OPT_LOCAL_INFILE,
            MYSQLI_INIT_COMMAND,
            MYSQLI_READ_DEFAULT_FILE,
            MYSQLI_READ_DEFAULT_GROUP,
237
        ];
238

239
        if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
240
            $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
241
        }
242

243
        $exceptionMsg = "%s option '%s' with value '%s'";
244 245

        foreach ($driverOptions as $option => $value) {
246
            if ($option === static::OPTION_FLAGS) {
247 248 249
                continue;
            }

250
            if (! in_array($option, $supportedDriverOptions, true)) {
251 252 253 254 255
                throw new MysqliException(
                    sprintf($exceptionMsg, 'Unsupported', $option, $value)
                );
            }

256
            if (@mysqli_options($this->conn, $option, $value)) {
257
                continue;
258
            }
259 260

            $msg  = sprintf($exceptionMsg, 'Failed to set', $option, $value);
261
            $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
262 263 264

            throw new MysqliException(
                $msg,
265 266
                $this->conn->sqlstate,
                $this->conn->errno
267
            );
268 269
        }
    }
270 271 272 273 274 275 276 277

    /**
     * Pings the server and re-connects when `mysqli.reconnect = 1`
     *
     * @return bool
     */
    public function ping()
    {
278
        return $this->conn->ping();
279
    }
280 281 282 283

    /**
     * Establish a secure connection
     *
284
     * @param mixed[] $params
285
     *
286 287
     * @throws MysqliException
     */
288
    private function setSecureConnection(array $params) : void
289 290 291 292 293 294 295 296 297 298
    {
        if (! isset($params['ssl_key']) &&
            ! isset($params['ssl_cert']) &&
            ! isset($params['ssl_ca']) &&
            ! isset($params['ssl_capath']) &&
            ! isset($params['ssl_cipher'])
        ) {
            return;
        }

299
        $this->conn->ssl_set(
300 301
            $params['ssl_key']    ?? null,
            $params['ssl_cert']   ?? null,
302 303 304 305 306
            $params['ssl_ca']     ?? null,
            $params['ssl_capath'] ?? null,
            $params['ssl_cipher'] ?? null
        );
    }
307
}