1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
4
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
5
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
6
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
7
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
8
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
9
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
10
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
11
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
12
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
13
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
14
|
|
|
* |
15
|
|
|
* This software consists of voluntary contributions made by many individuals |
16
|
|
|
* and is licensed under the MIT license. For more information, see |
17
|
|
|
* <http://www.doctrine-project.org>. |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace Doctrine\DBAL\Driver\Mysqli; |
21
|
|
|
|
22
|
|
|
use Doctrine\DBAL\Driver\Connection as Connection; |
23
|
|
|
use Doctrine\DBAL\Driver\PingableConnection; |
24
|
|
|
use Doctrine\DBAL\Driver\ServerInfoAwareConnection; |
25
|
|
|
use Doctrine\DBAL\ParameterType; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @author Kim Hemsø Rasmussen <[email protected]> |
29
|
|
|
* @author Till Klampaeckel <[email protected]> |
30
|
|
|
*/ |
31
|
|
|
class MysqliConnection implements Connection, PingableConnection, ServerInfoAwareConnection |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* Name of the option to set connection flags |
35
|
|
|
*/ |
36
|
|
|
const OPTION_FLAGS = 'flags'; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var \mysqli |
40
|
|
|
*/ |
41
|
|
|
private $_conn; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param array $params |
45
|
|
|
* @param string $username |
46
|
|
|
* @param string $password |
47
|
|
|
* @param array $driverOptions |
48
|
|
|
* |
49
|
|
|
* @throws \Doctrine\DBAL\Driver\Mysqli\MysqliException |
50
|
|
|
*/ |
51
|
|
|
public function __construct(array $params, $username, $password, array $driverOptions = []) |
52
|
|
|
{ |
53
|
|
|
$port = $params['port'] ?? ini_get('mysqli.default_port'); |
54
|
|
|
|
55
|
|
|
// Fallback to default MySQL port if not given. |
56
|
|
|
if ( ! $port) { |
57
|
|
|
$port = 3306; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
$socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket'); |
61
|
|
|
$dbname = $params['dbname'] ?? null; |
62
|
|
|
|
63
|
|
|
$flags = $driverOptions[static::OPTION_FLAGS] ?? null; |
64
|
|
|
|
65
|
|
|
$this->_conn = mysqli_init(); |
66
|
|
|
|
67
|
|
|
$this->setSecureConnection($params); |
68
|
|
|
$this->setDriverOptions($driverOptions); |
69
|
|
|
|
70
|
|
|
set_error_handler(function () {}); |
71
|
|
|
try { |
72
|
|
|
if ( ! $this->_conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) { |
73
|
|
|
throw new MysqliException($this->_conn->connect_error, $this->_conn->sqlstate ?? 'HY000', $this->_conn->connect_errno); |
74
|
|
|
} |
75
|
|
|
} finally { |
76
|
|
|
restore_error_handler(); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
if (isset($params['charset'])) { |
|
|
|
|
80
|
|
|
$this->_conn->set_charset($params['charset']); |
81
|
|
|
} |
82
|
|
|
} |
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
|
|
|
* @link https://jira.mariadb.org/browse/MDEV-4088 |
102
|
|
|
*/ |
103
|
|
|
public function getServerVersion() |
104
|
|
|
{ |
105
|
|
|
$serverInfos = $this->_conn->get_server_info(); |
106
|
|
|
if (false !== stripos($serverInfos, 'mariadb')) { |
107
|
|
|
return $serverInfos; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
$majorVersion = floor($this->_conn->server_version / 10000); |
111
|
|
|
$minorVersion = floor(($this->_conn->server_version - $majorVersion * 10000) / 100); |
112
|
|
|
$patchVersion = floor($this->_conn->server_version - $majorVersion * 10000 - $minorVersion * 100); |
113
|
|
|
|
114
|
|
|
return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* {@inheritdoc} |
119
|
|
|
*/ |
120
|
|
|
public function requiresQueryForServerVersion() |
121
|
|
|
{ |
122
|
|
|
return false; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* {@inheritdoc} |
127
|
|
|
*/ |
128
|
|
|
public function prepare($prepareString) |
129
|
|
|
{ |
130
|
|
|
return new MysqliStatement($this->_conn, $prepareString); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* {@inheritdoc} |
135
|
|
|
*/ |
136
|
|
|
public function query() |
137
|
|
|
{ |
138
|
|
|
$args = func_get_args(); |
139
|
|
|
$sql = $args[0]; |
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($statement) |
158
|
|
|
{ |
159
|
|
|
if (false === $this->_conn->query($statement)) { |
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; |
|
|
|
|
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Apply the driver options to the connection. |
218
|
|
|
* |
219
|
|
|
* @param array $driverOptions |
220
|
|
|
* |
221
|
|
|
* @throws MysqliException When one of of the options is not supported. |
222
|
|
|
* @throws MysqliException When applying doesn't work - e.g. due to incorrect value. |
223
|
|
|
*/ |
224
|
|
|
private function setDriverOptions(array $driverOptions = []) |
225
|
|
|
{ |
226
|
|
|
$supportedDriverOptions = [ |
227
|
|
|
\MYSQLI_OPT_CONNECT_TIMEOUT, |
228
|
|
|
\MYSQLI_OPT_LOCAL_INFILE, |
229
|
|
|
\MYSQLI_INIT_COMMAND, |
230
|
|
|
\MYSQLI_READ_DEFAULT_FILE, |
231
|
|
|
\MYSQLI_READ_DEFAULT_GROUP, |
232
|
|
|
]; |
233
|
|
|
|
234
|
|
|
if (defined('MYSQLI_SERVER_PUBLIC_KEY')) { |
235
|
|
|
$supportedDriverOptions[] = \MYSQLI_SERVER_PUBLIC_KEY; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$exceptionMsg = "%s option '%s' with value '%s'"; |
239
|
|
|
|
240
|
|
|
foreach ($driverOptions as $option => $value) { |
241
|
|
|
|
242
|
|
|
if ($option === static::OPTION_FLAGS) { |
243
|
|
|
continue; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
if (!in_array($option, $supportedDriverOptions, true)) { |
247
|
|
|
throw new MysqliException( |
248
|
|
|
sprintf($exceptionMsg, 'Unsupported', $option, $value) |
249
|
|
|
); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
if (@mysqli_options($this->_conn, $option, $value)) { |
253
|
|
|
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
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Pings the server and re-connects when `mysqli.reconnect = 1` |
269
|
|
|
* |
270
|
|
|
* @return bool |
271
|
|
|
*/ |
272
|
|
|
public function ping() |
273
|
|
|
{ |
274
|
|
|
return $this->_conn->ping(); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Establish a secure connection |
279
|
|
|
* |
280
|
|
|
* @param array $params |
281
|
|
|
* @throws MysqliException |
282
|
|
|
*/ |
283
|
|
|
private function setSecureConnection(array $params) |
284
|
|
|
{ |
285
|
|
|
if (! isset($params['ssl_key']) && |
286
|
|
|
! isset($params['ssl_cert']) && |
287
|
|
|
! isset($params['ssl_ca']) && |
288
|
|
|
! isset($params['ssl_capath']) && |
289
|
|
|
! isset($params['ssl_cipher']) |
290
|
|
|
) { |
291
|
|
|
return; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
$this->_conn->ssl_set( |
295
|
|
|
$params['ssl_key'] ?? null, |
296
|
|
|
$params['ssl_cert'] ?? null, |
297
|
|
|
$params['ssl_ca'] ?? null, |
298
|
|
|
$params['ssl_capath'] ?? null, |
299
|
|
|
$params['ssl_cipher'] ?? null |
300
|
|
|
); |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.