1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Class MySQLiDrv |
4
|
|
|
* |
5
|
|
|
* @filesource MySQLphp |
6
|
|
|
* @created 28.06.2017 |
7
|
|
|
* @package chillerlan\Database\Drivers |
8
|
|
|
* @author Smiley <[email protected]> |
9
|
|
|
* @copyright 2017 Smiley |
10
|
|
|
* @license MIT |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
namespace chillerlan\Database\Drivers; |
14
|
|
|
|
15
|
|
|
use chillerlan\Database\Dialects\MySQL; |
16
|
|
|
use chillerlan\Database\Result; |
17
|
|
|
use Exception; |
18
|
|
|
use mysqli; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @property mysqli $db |
22
|
|
|
*/ |
23
|
|
|
class MySQLiDrv extends DriverAbstract{ |
24
|
|
|
|
25
|
|
|
protected $dialect = MySQL::class; |
26
|
|
|
|
27
|
|
|
/** @inheritdoc */ |
28
|
|
|
public function connect():DriverInterface{ |
29
|
|
|
|
30
|
|
|
if($this->db instanceof mysqli){ |
31
|
|
|
return $this; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
try{ |
35
|
|
|
$this->db = mysqli_init(); |
36
|
|
|
|
37
|
|
|
$this->db->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->options->mysqli_timeout); |
38
|
|
|
|
39
|
|
|
// @codeCoverageIgnoreStart |
40
|
|
|
if($this->options->use_ssl){ |
41
|
|
|
$this->db->ssl_set( |
42
|
|
|
$this->options->ssl_key, |
43
|
|
|
$this->options->ssl_cert, |
44
|
|
|
$this->options->ssl_ca, |
45
|
|
|
$this->options->ssl_capath, |
46
|
|
|
$this->options->ssl_cipher |
47
|
|
|
); |
48
|
|
|
} |
49
|
|
|
// @codeCoverageIgnoreEnd |
50
|
|
|
|
51
|
|
|
$this->db->real_connect( |
52
|
|
|
$this->options->host, |
53
|
|
|
$this->options->username, |
54
|
|
|
$this->options->password, |
55
|
|
|
$this->options->database, |
56
|
|
|
(int)$this->options->port, |
57
|
|
|
$this->options->socket |
58
|
|
|
); |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @see https://mathiasbynens.be/notes/mysql-utf8mb4 How to support full Unicode in MySQL |
62
|
|
|
*/ |
63
|
|
|
$this->db->set_charset($this->options->mysql_charset); |
64
|
|
|
|
65
|
|
|
return $this; |
66
|
|
|
} |
67
|
|
|
catch(Exception $e){ |
68
|
|
|
throw new DriverException('db error: [MySQLiDrv]: '.$e->getMessage()); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** @inheritdoc */ |
74
|
|
|
public function disconnect():bool{ |
75
|
|
|
|
76
|
|
|
if($this->db instanceof mysqli){ |
77
|
|
|
$this->db->close(); |
78
|
|
|
|
79
|
|
|
$this->db = null; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
return true; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** @inheritdoc */ |
86
|
|
|
public function getClientInfo():string{ |
87
|
|
|
return $this->db->client_info; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** @inheritdoc */ |
91
|
|
|
public function getServerInfo():string{ |
92
|
|
|
return $this->db->server_info; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** @inheritdoc */ |
96
|
|
|
protected function __escape(string $data):string{ |
97
|
|
|
return '\''.$this->db->real_escape_string($data).'\''; // emulate PDO |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** @inheritdoc */ |
101
|
|
|
protected function raw_query(string $sql, string $index = null, bool $assoc = null){ |
102
|
|
|
$result = $this->db->query($sql); |
103
|
|
|
|
104
|
|
|
if(is_bool($result)){ |
105
|
|
|
|
106
|
|
|
if($this->db->errno !== 0 || !$result){ |
107
|
|
|
throw new DriverException($this->db->error, $this->db->errno); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
return $result; // @codeCoverageIgnore |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
$r = $this->getResult([$result, 'fetch_'.(($assoc ?? true) ? 'assoc' : 'row')], [], $index, $assoc); |
114
|
|
|
|
115
|
|
|
$result->free(); |
116
|
|
|
|
117
|
|
|
return $r; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** @inheritdoc */ |
121
|
|
|
protected function prepared_query(string $sql, array $values = null, string $index = null, bool $assoc = null){ |
122
|
|
|
$assoc = $assoc ?? true; |
123
|
|
|
$stmt = $this->db->stmt_init(); |
124
|
|
|
$stmt->prepare($sql); |
125
|
|
|
$this->stmtError($this->db->errno, $this->db->error); |
126
|
|
|
|
127
|
|
|
if(count($values) > 0){ |
128
|
|
|
call_user_func_array([$stmt, 'bind_param'], $this->getReferences($values)); |
|
|
|
|
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
$stmt->execute(); |
132
|
|
|
|
133
|
|
|
$result = $stmt->result_metadata(); |
134
|
|
|
|
135
|
|
|
if(is_bool($result)){ |
136
|
|
|
return true; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
// get the columns and their references |
140
|
|
|
// http://php.net/manual/mysqli-stmt.bind-result.php |
141
|
|
|
$cols = []; |
142
|
|
|
$refs = []; |
143
|
|
|
|
144
|
|
|
foreach($result->fetch_fields() as $k => $field){ |
145
|
|
|
$refs[] = &$cols[$assoc ? $field->name : $k]; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
call_user_func_array([$stmt, 'bind_result'], $refs); |
149
|
|
|
|
150
|
|
|
// fetch the data |
151
|
|
|
$output = new Result(null, $this->convert_encoding_src, $this->convert_encoding_dest); |
152
|
|
|
$i = 0; |
153
|
|
|
|
154
|
|
|
while($stmt->fetch()){ |
155
|
|
|
$row = []; |
156
|
|
|
$key = $i; |
157
|
|
|
|
158
|
|
|
foreach($cols as $field => $data){ |
159
|
|
|
$row[$field] = $data; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
if($assoc && !empty($index)){ |
163
|
|
|
$key = $row[$index] ?? $i; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$output[$key] = $row; |
167
|
|
|
$i++; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
// KTHXBYE! |
171
|
|
|
$stmt->free_result(); |
172
|
|
|
$stmt->close(); |
173
|
|
|
|
174
|
|
|
return $i === 0 ? true : $output; // @todo: return proper Result object in all cases |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** @inheritdoc */ |
178
|
|
|
protected function multi_query(string $sql, array $values){ |
179
|
|
|
$stmt = $this->db->stmt_init(); |
180
|
|
|
$stmt->prepare($sql); |
181
|
|
|
$this->stmtError($this->db->errno, $this->db->error); |
182
|
|
|
|
183
|
|
|
foreach($values as $row){ |
184
|
|
|
call_user_func_array([$stmt, 'bind_param'], $this->getReferences($row)); |
185
|
|
|
|
186
|
|
|
$stmt->execute(); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
$stmt->close(); |
190
|
|
|
|
191
|
|
|
return true; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** @inheritdoc */ |
195
|
|
|
protected function multi_callback_query(string $sql, iterable $data, $callback){ |
196
|
|
|
$stmt = $this->db->stmt_init(); |
197
|
|
|
$stmt->prepare($sql); |
198
|
|
|
$this->stmtError($this->db->errno, $this->db->error); |
199
|
|
|
|
200
|
|
|
foreach($data as $k => $row){ |
201
|
|
|
$row = call_user_func_array($callback, [$row, $k]); |
202
|
|
|
|
203
|
|
|
if($row !== false && !empty($row)){ |
204
|
|
|
call_user_func_array([$stmt, 'bind_param'], $this->getReferences($row)); |
205
|
|
|
|
206
|
|
|
$stmt->execute(); |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
$stmt->close(); |
211
|
|
|
|
212
|
|
|
return true; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @param int $errno |
217
|
|
|
* @param string $errstr |
218
|
|
|
* |
219
|
|
|
* @throws \chillerlan\Database\Drivers\DriverException |
220
|
|
|
*/ |
221
|
|
|
protected function stmtError(int $errno, string $errstr):void{ |
222
|
|
|
|
223
|
|
|
if($errno !== 0){ |
224
|
|
|
throw new DriverException($errstr, $errno); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Copies an array to an array of referenced values |
231
|
|
|
* |
232
|
|
|
* @param array $row |
233
|
|
|
* |
234
|
|
|
* @return array |
235
|
|
|
* @see http://php.net/manual/mysqli-stmt.bind-param.php |
236
|
|
|
*/ |
237
|
|
|
protected function getReferences(array $row){ |
238
|
|
|
$references = []; |
239
|
|
|
$types = []; |
240
|
|
|
|
241
|
|
|
foreach($row as &$field){ |
242
|
|
|
$type = gettype($field); |
243
|
|
|
|
244
|
|
|
if($type === 'integer'){ |
245
|
|
|
$types[] = 'i'; |
246
|
|
|
} |
247
|
|
|
elseif($type === 'double'){ |
248
|
|
|
$types[] = 'd'; |
249
|
|
|
} |
250
|
|
|
else{ |
251
|
|
|
$types[] = 's'; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
$references[] = &$field; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
array_unshift($references, implode('', $types)); |
258
|
|
|
|
259
|
|
|
return $references; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
} |
263
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.