Issues (68)

src/Drivers/MySQLiDrv.php (5 issues)

Labels
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
 * @noinspection PhpComposerExtensionStubsInspection
13
 */
14
15
namespace chillerlan\Database\Drivers;
16
17
use chillerlan\Database\Dialects\MySQL;
18
use chillerlan\Database\Result;
19
use Exception;
20
use mysqli;
21
22
/**
23
 * @property mysqli $db
24
 */
25
class MySQLiDrv extends DriverAbstract{
26
27
	protected string $dialect = MySQL::class;
28
29
	/** @inheritdoc */
30
	public function connect():DriverInterface{
31
32
		if($this->db instanceof mysqli){
0 ignored issues
show
$this->db is always a sub-type of mysqli.
Loading history...
33
			return $this;
34
		}
35
36
		try{
37
			$this->db = mysqli_init();
38
39
			$this->db->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->options->mysqli_timeout);
40
41
			// @codeCoverageIgnoreStart
42
			if($this->options->use_ssl){
43
				$this->db->ssl_set(
44
					$this->options->ssl_key,
45
					$this->options->ssl_cert,
46
					$this->options->ssl_ca,
47
					$this->options->ssl_capath,
48
					$this->options->ssl_cipher
49
				);
50
			}
51
			// @codeCoverageIgnoreEnd
52
53
			$this->db->real_connect(
54
				$this->options->host,
55
				$this->options->username,
56
				$this->options->password,
57
				$this->options->database,
58
				!empty($this->options->port) ? (int)$this->options->port : null,
59
				$this->options->socket
60
			);
61
62
			/**
63
			 * @see https://mathiasbynens.be/notes/mysql-utf8mb4 How to support full Unicode in MySQL
64
			 */
65
			$this->db->set_charset($this->options->mysql_charset);
66
67
			return $this;
68
		}
69
		catch(Exception $e){
70
			throw new DriverException('db error: [MySQLiDrv]: '.$e->getMessage());
71
		}
72
73
	}
74
75
	/** @inheritdoc */
76
	public function disconnect():bool{
77
78
		if($this->db instanceof mysqli){
0 ignored issues
show
$this->db is always a sub-type of mysqli.
Loading history...
79
			$this->db->close();
80
81
			$this->db = null;
82
		}
83
84
		return true;
85
	}
86
87
	/** @inheritdoc */
88
	public function getClientInfo():string{
89
		return $this->db->client_info;
90
	}
91
92
	/** @inheritdoc */
93
	public function getServerInfo():string{
94
		return $this->db->server_info;
95
	}
96
97
	/** @inheritdoc */
98
	protected function __escape(string $data):string{
99
		return '\''.$this->db->real_escape_string($data).'\''; // emulate PDO
100
	}
101
102
	/** @inheritdoc */
103
	protected function raw_query(string $sql, string $index = null, bool $assoc = null){
104
		$result = $this->db->query($sql);
105
106
		if(is_bool($result)){
107
108
			if($this->db->errno !== 0 || !$result){
109
				throw new DriverException($this->db->error, $this->db->errno);
110
			}
111
112
			return $result; // @codeCoverageIgnore
113
		}
114
115
		$r = $this->getResult([$result, 'fetch_'.(($assoc ?? true) ? 'assoc' : 'row')], [], $index, $assoc);
116
117
		$result->free();
118
119
		return $r;
120
	}
121
122
	/** @inheritdoc */
123
	protected function prepared_query(string $sql, array $values = null, string $index = null, bool $assoc = null){
124
		$assoc = $assoc ?? true;
125
		$stmt = $this->db->stmt_init();
126
		$stmt->prepare($sql);
127
		$this->stmtError($this->db->errno, $this->db->error);
128
129
		if(count($values) > 0){
0 ignored issues
show
It seems like $values can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, 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

129
		if(count(/** @scrutinizer ignore-type */ $values) > 0){
Loading history...
130
			call_user_func_array([$stmt, 'bind_param'], $this->getReferences($values));
0 ignored issues
show
It seems like $values can also be of type null; however, parameter $row of chillerlan\Database\Driv...QLiDrv::getReferences() does only seem to accept array, 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

130
			call_user_func_array([$stmt, 'bind_param'], $this->getReferences(/** @scrutinizer ignore-type */ $values));
Loading history...
131
		}
132
133
		$stmt->execute();
134
135
		$result = $stmt->result_metadata();
136
137
		if(is_bool($result)){
0 ignored issues
show
The condition is_bool($result) is always false.
Loading history...
138
			// https://www.php.net/manual/mysqli-stmt.result-metadata.php#97338
139
			// the query did not produce a result, everything ok.
140
			return true;
141
		}
142
143
		// get the columns and their references
144
		// http://php.net/manual/mysqli-stmt.bind-result.php
145
		$cols = [];
146
		$refs = [];
147
148
		foreach($result->fetch_fields() as $k => $field){
149
			$refs[] = &$cols[$assoc ? $field->name : $k];
150
		}
151
152
		call_user_func_array([$stmt, 'bind_result'], $refs);
153
154
		// fetch the data
155
		$output = new Result(null, $this->convert_encoding_src, $this->convert_encoding_dest);
156
		$i      = 0;
157
158
		while($stmt->fetch()){
159
			$row = [];
160
			$key = $i;
161
162
			foreach($cols as $field => $data){
163
				$row[$field] = $data;
164
			}
165
166
			if($assoc && !empty($index)){
167
				$key = $row[$index] ?? $i;
168
			}
169
170
			$output[$key] = $row;
171
			$i++;
172
		}
173
174
		// KTHXBYE!
175
		$stmt->free_result();
176
		$stmt->close();
177
178
		return $i === 0 ? true : $output; // @todo: return proper Result object in all cases
179
	}
180
181
	/** @inheritdoc */
182
	protected function multi_query(string $sql, array $values){
183
		$stmt = $this->db->stmt_init();
184
		$stmt->prepare($sql);
185
		$this->stmtError($this->db->errno, $this->db->error);
186
187
		foreach($values as $row){
188
			call_user_func_array([$stmt, 'bind_param'], $this->getReferences($row));
189
190
			$stmt->execute();
191
		}
192
193
		$stmt->close();
194
195
		return true;
196
	}
197
198
	/** @inheritdoc */
199
	protected function multi_callback_query(string $sql, iterable $data, $callback){
200
		$stmt = $this->db->stmt_init();
201
		$stmt->prepare($sql);
202
		$this->stmtError($this->db->errno, $this->db->error);
203
204
		foreach($data as $k => $row){
205
			$row = call_user_func_array($callback, [$row, $k]);
206
207
			if($row !== false && !empty($row)){
208
				call_user_func_array([$stmt, 'bind_param'], $this->getReferences($row));
209
210
				$stmt->execute();
211
			}
212
		}
213
214
		$stmt->close();
215
216
		return true;
217
	}
218
219
	/**
220
	 * @param int    $errno
221
	 * @param string $errstr
222
	 *
223
	 * @throws \chillerlan\Database\Drivers\DriverException
224
	 */
225
	protected function stmtError(int $errno, string $errstr):void{
226
227
		if($errno !== 0){
228
			throw new DriverException($errstr, $errno);
229
		}
230
231
	}
232
233
	/**
234
	 * Copies an array to an array of referenced values
235
	 *
236
	 * @param array $row
237
	 *
238
	 * @return array
239
	 * @see http://php.net/manual/mysqli-stmt.bind-param.php
240
	 */
241
	protected function getReferences(array $row){
242
		$references = [];
243
		$types      = [];
244
245
		foreach($row as &$field){
246
			$type = gettype($field);
247
248
			if($type === 'integer'){
249
				$types[] = 'i';
250
			}
251
			elseif($type === 'double'){
252
				$types[] = 'd';
253
			}
254
			else{
255
				$types[] = 's';
256
			}
257
258
			$references[] = &$field;
259
		}
260
261
		array_unshift($references, implode('', $types));
262
263
		return $references;
264
	}
265
266
}
267