Completed
Push — master ( fab357...0ba320 )
by Nazar
07:50
created

MySQLi::affected()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2017, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\DB;
9
use
10
	mysqli_result,
11
	mysqli_stmt;
12
13
class MySQLi extends _Abstract {
14
	/**
15
	 * @var \MySQLi Instance of DB connection
16
	 */
17
	protected $instance;
18
	/**
19
	 * @inheritdoc
20
	 */
21
	public function __construct ($database, $user = '', $password = '', $host = 'localhost', $prefix = '') {
22
		$start = microtime(true);
23
		/**
24
		 * Parsing of $host variable, detecting port and persistent connection
25
		 */
26
		list($host, $port) = $this->get_host_and_port($host);
27
		$this->instance = new \MySQLi($host, $user, $password, $database, $port);
28
		if (!is_object($this->instance) || $this->instance->connect_errno) {
29
			return;
30
		}
31
		$this->database = $database;
32
		/**
33
		 * Changing DB charset if necessary
34
		 */
35
		if ($this->instance->character_set_name() != 'utf8mb4') {
36
			$this->instance->set_charset('utf8mb4');
37
		}
38
		/**
39
		 * Force strict mode
40
		 */
41
		$this->instance->query(
42
			"SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'"
43
		);
44
		$this->connected       = true;
45
		$this->connecting_time = microtime(true) - $start;
46
		$this->db_type         = 'mysql';
47
		$this->prefix          = $prefix;
48
	}
49
	/**
50
	 * Parse host string into host and port separately
51
	 *
52
	 * Understands `p:` prefix for persistent connections
53
	 *
54
	 * @param string $host_string
55
	 *
56
	 * @return array
57
	 */
58
	protected function get_host_and_port ($host_string) {
59
		$host = explode(':', $host_string);
60
		$port = ini_get('mysqli.default_port') ?: 3306;
61
		switch (count($host)) {
62
			case 1:
63
				$host = $host[0];
64
				break;
65
			case 2:
66
				if ($host[0] == 'p') {
67
					$host = "$host[0]:$host[1]";
68
				} else {
69
					list($host, $port) = $host;
70
				}
71
				break;
72
			case 3:
73
				$port = $host[2];
74
				$host = "$host[0]:$host[1]";
75
		}
76
		return [$host, $port];
77
	}
78
	/**
79
	 * @inheritdoc
80
	 *
81
	 * @return bool|mysqli_result|mysqli_stmt
82
	 */
83
	protected function q_internal ($query, $parameters = []) {
84
		if (!$query) {
85
			return false;
86
		}
87
		$stmt = $this->q_internal_once($query, $parameters);
88
		/*
89
		 * In case of MySQL Client error try once again
90
		 */
91
		if (
92
			!$stmt &&
93
			$this->instance->errno >= 2000 &&
94
			$this->instance->ping()
95
		) {
96
			$stmt = $this->q_internal_once($query, $parameters);
97
		}
98
		return $stmt;
99
	}
100
	/**
101
	 * @param string $query
102
	 * @param array  $parameters
103
	 *
104
	 * @return bool|mysqli_result|mysqli_stmt
105
	 */
106
	protected function q_internal_once ($query, $parameters) {
107
		if (!$parameters) {
108
			return $this->instance->query($query);
109
		}
110
		$stmt = $this->instance->prepare($query);
111
		if (!$stmt) {
112
			return false;
113
		}
114
		// Allows to provide more parameters for prepared statements than needed
115
		$local_parameters = array_slice($parameters, 0, substr_count($query, '?'));
116
		if (!$this->q_internal_once_bind_param($stmt, $local_parameters)) {
117
			return false;
118
		}
119
		$result = $stmt->execute();
120
		/**
121
		 * Return result only for SELECT queries, boolean otherwise
122
		 */
123
		return $result && strpos($query, 'SELECT') === 0 ? $stmt : $result;
124
	}
125
	/**
126
	 * @param mysqli_stmt $stmt
127
	 * @param array       $local_parameters
128
	 *
129
	 * @return bool
130
	 */
131
	protected function q_internal_once_bind_param ($stmt, $local_parameters) {
132
		/**
133
		 * Hack: Until https://github.com/facebook/hhvm/issues/6229 is fixed
134
		 *
135
		 * Some black magic to please HHVM, ideally only last line is necessary
136
		 */
137
		$tmp = [];
138
		foreach (array_keys($local_parameters) as $key) {
139
			$tmp[$key] = &$local_parameters[$key];
140
		}
141
		return $stmt->bind_param(
142
			str_repeat('s', count($local_parameters)),
143
			...$local_parameters
144
		);
145
	}
146
	/**
147
	 * @inheritdoc
148
	 *
149
	 * @param false|mysqli_result|mysqli_stmt $query_result_stmt
150
	 */
151
	public function f ($query_result_stmt, $single_column = false, $array = false, $indexed = false) {
152
		if ($query_result_stmt instanceof mysqli_result) {
153
			return $this->f_result($query_result_stmt, $single_column, $array, $indexed);
154
		}
155
		if ($query_result_stmt instanceof mysqli_stmt) {
156
			return $this->f_stmt($query_result_stmt, $single_column, $array, $indexed);
157
		}
158
		return false;
159
	}
160
	/**
161
	 * @param mysqli_result $query_result
162
	 * @param bool          $single_column
163
	 * @param bool          $array
164
	 * @param bool          $indexed
165
	 *
166
	 * @return array|bool|mixed
167
	 */
168
	protected function f_result ($query_result, $single_column, $array, $indexed) {
169
		$result_type = $single_column || $indexed ? MYSQLI_NUM : MYSQLI_ASSOC;
170
		if ($array) {
171
			$result = [];
172
			while ($current = $query_result->fetch_array($result_type)) {
173
				$result[] = $single_column ? $current[0] : $current;
174
			}
175
			$this->free($query_result);
176
			return $result;
177
		}
178
		$result = $query_result->fetch_array($result_type);
179
		if ($result === null) {
180
			$result = false;
181
		}
182
		return $single_column && $result ? $result[0] : $result;
183
	}
184
	/**
185
	 * @param mysqli_stmt $stmt
186
	 * @param bool        $single_column
187
	 * @param bool        $array
188
	 * @param bool        $indexed
189
	 *
190
	 * @return array|bool|mixed
191
	 */
192
	protected function f_stmt ($stmt, $single_column, $array, $indexed) {
193
		$meta    = $stmt->result_metadata();
194
		$result  = [];
195
		$columns = [];
196
		while ($field = $meta->fetch_field()) {
197
			$result[]  = null;
198
			$columns[] = $field->name;
199
		}
200
		if (!$stmt->store_result() || !$this->f_stmt_bind_result($stmt, $result)) {
201
			return false;
202
		}
203
		if ($array) {
204
			$return_result = [];
205
			while ($current_result = $this->f_stmt_internal($stmt, $result, $single_column, $indexed, $columns)) {
206
				$return_result[] = $current_result;
207
			}
208
			$this->free($stmt);
209
			return $return_result;
210
		}
211
		return $this->f_stmt_internal($stmt, $result, $single_column, $indexed, $columns);
212
	}
213
	/**
214
	 * @param mysqli_stmt $stmt
215
	 * @param array       $result
216
	 *
217
	 * @return bool
218
	 */
219
	protected function f_stmt_bind_result ($stmt, &$result) {
220
		/**
221
		 * Hack: Until https://github.com/facebook/hhvm/issues/6229 is fixed
222
		 *
223
		 * Some black magic to please HHVM, ideally only last line is necessary
224
		 */
225
		$tmp = [];
226
		foreach (array_keys($result) as $key) {
227
			$tmp[$key] = &$result[$key];
228
		}
229
		return $stmt->bind_result(...$result);
230
	}
231
	/**
232
	 * @param mysqli_stmt $stmt
233
	 * @param array       $result
234
	 * @param bool        $single_column
235
	 * @param bool        $indexed
236
	 * @param string[]    $columns
237
	 *
238
	 * @return array|bool
239
	 */
240
	protected function f_stmt_internal ($stmt, $result, $single_column, $indexed, $columns) {
241
		if (!$stmt->fetch()) {
242
			return false;
243
		}
244
		if ($single_column && $result) {
245
			return $result[0];
246
		}
247
		// Hack: `$result`'s values are all references, we need to dereference them into plain values
248
		$new_result = [];
249
		foreach ($result as $i => $v) {
250
			$new_result[$i] = $v;
251
		}
252
		$result = $new_result;
253
		if ($indexed) {
254
			return $result;
255
		}
256
		return array_combine($columns, $result);
257
	}
258
	/**
259
	 * @inheritdoc
260
	 */
261
	public function id () {
262
		return $this->instance->insert_id;
263
	}
264
	/**
265
	 * @inheritdoc
266
	 */
267
	public function affected () {
268
		return $this->instance->affected_rows;
269
	}
270
	/**
271
	 * @inheritdoc
272
	 *
273
	 * @param false|mysqli_result|mysqli_stmt $query_result_stmt
274
	 */
275
	public function free ($query_result_stmt) {
276
		if ($query_result_stmt instanceof mysqli_result) {
277
			$query_result_stmt->free();
278
		}
279
		if ($query_result_stmt instanceof mysqli_stmt) {
280
			$query_result_stmt->free_result();
281
		}
282
		return true;
283
	}
284
	/**
285
	 * @inheritdoc
286
	 */
287
	public function columns ($table, $like = false) {
288
		if (!$table) {
289
			return false;
290
		}
291
		if ($like) {
292
			$like    = $this->s($like);
293
			$columns = $this->qfas("SHOW COLUMNS FROM `$table` LIKE $like") ?: [];
294
		} else {
295
			$columns = $this->qfas("SHOW COLUMNS FROM `$table`") ?: [];
296
		}
297
		return $columns;
298
	}
299
	/**
300
	 * @inheritdoc
301
	 */
302
	public function tables ($like = false) {
303
		if ($like) {
304
			$like = $this->s($like);
305
			return $this->qfas("SHOW TABLES FROM `$this->database` LIKE $like") ?: [];
306
		} else {
307
			return $this->qfas("SHOW TABLES FROM `$this->database`") ?: [];
308
		}
309
	}
310
	/**
311
	 * @inheritdoc
312
	 */
313
	protected function s_internal ($string, $single_quotes_around) {
314
		$return = $this->instance->real_escape_string($string);
315
		return $single_quotes_around ? "'$return'" : $return;
316
	}
317
	/**
318
	 * @inheritdoc
319
	 */
320
	public function server () {
321
		return $this->instance->server_info;
322
	}
323
	/**
324
	 * @inheritdoc
325
	 */
326
	public function __destruct () {
327
		if ($this->connected && is_object($this->instance)) {
328
			$this->instance->close();
329
			$this->connected = false;
330
		}
331
	}
332
}
333