Completed
Push — master ( 62a6e8...e0fd75 )
by smiley
02:52
created

MySQLiDrv::stmtError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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));
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 121 can also be of type null; however, chillerlan\Database\Driv...QLiDrv::getReferences() does only seem to accept array, maybe add an additional type check?

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.

Loading history...
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