MySQLi::f_stmt()   B
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.5625

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 8
nop 4
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
ccs 15
cts 20
cp 0.75
crap 6.5625
1
<?php
2
/**
3
 * @package CleverStyle Framework
4
 * @author  Nazar Mokrynskyi <[email protected]>
5
 * @license 0BSD
6
 */
7
namespace cs\DB;
8
use
9
	mysqli_result,
10
	mysqli_stmt;
11
12
class MySQLi extends _Abstract {
13
	/**
14
	 * @var \MySQLi Instance of DB connection
15
	 */
16
	protected $instance;
17
	/**
18
	 * @inheritdoc
19
	 */
20 32
	public function __construct ($database, $user = '', $password = '', $host = 'localhost', $prefix = '') {
21 32
		$start = microtime(true);
22
		/**
23
		 * Parsing of $host variable, detecting port and persistent connection
24
		 */
25 32
		list($host, $port) = $this->get_host_and_port($host);
26 32
		$this->instance = new \MySQLi($host, $user, $password, $database, $port);
27 32
		if (!is_object($this->instance) || $this->instance->connect_errno) {
28 1
			return;
29
		}
30 32
		$this->database = $database;
31
		/**
32
		 * Changing DB charset if necessary
33
		 */
34 32
		if ($this->instance->character_set_name() != 'utf8mb4') {
35 32
			$this->instance->set_charset('utf8mb4');
36
		}
37
		/**
38
		 * Force strict mode
39
		 */
40 32
		$this->instance->query(
41 32
			"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'"
42
		);
43 32
		$this->connected       = true;
44 32
		$this->connecting_time = microtime(true) - $start;
45 32
		$this->db_type         = 'mysql';
46 32
		$this->prefix          = $prefix;
47 32
	}
48
	/**
49
	 * Parse host string into host and port separately
50
	 *
51
	 * Understands `p:` prefix for persistent connections
52
	 *
53
	 * @param string $host_string
54
	 *
55
	 * @return array
56
	 */
57 32
	protected function get_host_and_port ($host_string) {
58 32
		$host = explode(':', $host_string);
59 32
		$port = ini_get('mysqli.default_port') ?: 3306;
60 32
		switch (count($host)) {
61 32
			case 1:
62 32
				$host = $host[0];
63 32
				break;
64 1
			case 2:
65 1
				if ($host[0] == 'p') {
66 1
					$host = "$host[0]:$host[1]";
67
				} else {
68 1
					list($host, $port) = $host;
69
				}
70 1
				break;
71 1
			case 3:
72 1
				$port = $host[2];
73 1
				$host = "$host[0]:$host[1]";
74
		}
75 32
		return [$host, $port];
76
	}
77
	/**
78
	 * @inheritdoc
79
	 *
80
	 * @return bool|mysqli_result|mysqli_stmt
81
	 */
82 32
	protected function q_internal ($query, $parameters = []) {
83 32
		if (!$query) {
84 1
			return false;
85
		}
86 32
		$stmt = $this->q_internal_once($query, $parameters);
87
		/*
88
		 * In case of MySQL Client error try once again
89
		 */
90
		if (
91 32
			!$stmt &&
92 32
			$this->instance->errno >= 2000 &&
93 32
			$this->instance->ping()
94
		) {
95
			$stmt = $this->q_internal_once($query, $parameters);
96
		}
97 32
		return $stmt;
98
	}
99
	/**
100
	 * @param string $query
101
	 * @param array  $parameters
102
	 *
103
	 * @return bool|mysqli_result|mysqli_stmt
104
	 */
105 32
	protected function q_internal_once ($query, $parameters) {
106 32
		if (!$parameters) {
107 32
			return $this->instance->query($query);
108
		}
109 27
		$stmt = $this->instance->prepare($query);
110 27
		if (!$stmt) {
111
			return false;
112
		}
113
		// Allows to provide more parameters for prepared statements than needed
114 27
		$local_parameters = array_slice($parameters, 0, substr_count($query, '?'));
115 27
		if (!$this->q_internal_once_bind_param($stmt, $local_parameters)) {
116
			return false;
117
		}
118 27
		$result = $stmt->execute();
119
		/**
120
		 * Return result only for SELECT queries, boolean otherwise
121
		 */
122 27
		return $result && strpos($query, 'SELECT') === 0 ? $stmt : $result;
123
	}
124
	/**
125
	 * @param mysqli_stmt $stmt
126
	 * @param array       $local_parameters
127
	 *
128
	 * @return bool
129
	 */
130 27
	protected function q_internal_once_bind_param ($stmt, $local_parameters) {
131 27
		return $stmt->bind_param(
132 27
			str_repeat('s', count($local_parameters)),
133 27
			...$local_parameters
0 ignored issues
show
Bug introduced by
$local_parameters is expanded, but the parameter $var1 of mysqli_stmt::bind_param() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
			/** @scrutinizer ignore-type */ ...$local_parameters
Loading history...
134
		);
135
	}
136
	/**
137
	 * @inheritdoc
138
	 *
139
	 * @param false|mysqli_result|mysqli_stmt $query_result_stmt
140
	 */
141 32
	public function f ($query_result_stmt, $single_column = false, $array = false, $indexed = false) {
142 32
		if ($query_result_stmt instanceof mysqli_result) {
143 30
			return $this->f_result($query_result_stmt, $single_column, $array, $indexed);
144
		}
145 25
		if ($query_result_stmt instanceof mysqli_stmt) {
146 25
			return $this->f_stmt($query_result_stmt, $single_column, $array, $indexed);
147
		}
148 1
		return false;
149
	}
150
	/**
151
	 * @param mysqli_result $query_result
152
	 * @param bool          $single_column
153
	 * @param bool          $array
154
	 * @param bool          $indexed
155
	 *
156
	 * @return array|bool|mixed
157
	 */
158 30
	protected function f_result ($query_result, $single_column, $array, $indexed) {
159 30
		$result_type = $single_column || $indexed ? MYSQLI_NUM : MYSQLI_ASSOC;
160 30
		if ($array) {
161 21
			$result = [];
162 21
			while ($current = $query_result->fetch_array($result_type)) {
163 20
				$result[] = $single_column ? $current[0] : $current;
164
			}
165 21
			$this->free($query_result);
166 21
			return $result;
167
		}
168 26
		$result = $query_result->fetch_array($result_type);
169 26
		if ($result === null) {
170 22
			$result = false;
171
		}
172 26
		return $single_column && $result ? $result[0] : $result;
173
	}
174
	/**
175
	 * @param mysqli_stmt $stmt
176
	 * @param bool        $single_column
177
	 * @param bool        $array
178
	 * @param bool        $indexed
179
	 *
180
	 * @return array|bool|mixed
181
	 */
182 25
	protected function f_stmt ($stmt, $single_column, $array, $indexed) {
183 25
		$meta    = $stmt->result_metadata();
184 25
		$result  = [];
185 25
		$columns = [];
186 25
		while ($field = $meta->fetch_field()) {
187 25
			$result[]  = null;
188 25
			$columns[] = $field->name;
189
		}
190 25
		if (!$stmt->store_result() || !$this->f_stmt_bind_result($stmt, $result)) {
191
			return false;
192
		}
193 25
		if ($array) {
194 9
			$return_result = [];
195 9
			while ($current_result = $this->f_stmt_internal($stmt, $result, $single_column, $indexed, $columns)) {
196 9
				$return_result[] = $current_result;
197
			}
198 9
			$this->free($stmt);
199 9
			return $return_result;
200
		}
201 25
		return $this->f_stmt_internal($stmt, $result, $single_column, $indexed, $columns);
202
	}
203
	/**
204
	 * @param mysqli_stmt $stmt
205
	 * @param array       $result
206
	 *
207
	 * @return bool
208
	 */
209 25
	protected function f_stmt_bind_result ($stmt, &$result) {
210 25
		return $stmt->bind_result(...$result);
0 ignored issues
show
Bug introduced by
$result is expanded, but the parameter $var1 of mysqli_stmt::bind_result() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

210
		return $stmt->bind_result(/** @scrutinizer ignore-type */ ...$result);
Loading history...
211
	}
212
	/**
213
	 * @param mysqli_stmt $stmt
214
	 * @param array       $result
215
	 * @param bool        $single_column
216
	 * @param bool        $indexed
217
	 * @param string[]    $columns
218
	 *
219
	 * @return array|bool
220
	 */
221 25
	protected function f_stmt_internal ($stmt, $result, $single_column, $indexed, $columns) {
222 25
		if (!$stmt->fetch()) {
223 10
			return false;
224
		}
225 25
		if ($single_column && $result) {
226 8
			return $result[0];
227
		}
228
		// Hack: `$result`'s values are all references, we need to dereference them into plain values
229 24
		$new_result = [];
230 24
		foreach ($result as $i => $v) {
231 24
			$new_result[$i] = $v;
232
		}
233 24
		$result = $new_result;
234 24
		if ($indexed) {
235 1
			return $result;
236
		}
237 24
		return array_combine($columns, $result);
238
	}
239
	/**
240
	 * @inheritdoc
241
	 */
242 24
	public function id () {
243 24
		return $this->instance->insert_id;
244
	}
245
	/**
246
	 * @inheritdoc
247
	 */
248 1
	public function affected () {
249 1
		return $this->instance->affected_rows;
250
	}
251
	/**
252
	 * @inheritdoc
253
	 *
254
	 * @param false|mysqli_result|mysqli_stmt $query_result_stmt
255
	 */
256 27
	public function free ($query_result_stmt) {
257 27
		if ($query_result_stmt instanceof mysqli_result) {
258 21
			$query_result_stmt->free();
259
		}
260 27
		if ($query_result_stmt instanceof mysqli_stmt) {
261 9
			$query_result_stmt->free_result();
262
		}
263 27
		return true;
264
	}
265
	/**
266
	 * @inheritdoc
267
	 */
268 3
	public function columns ($table, $like = false) {
269 3
		if (!$table) {
270 1
			return false;
271
		}
272 3
		if ($like) {
273 1
			$like    = $this->s($like);
274 1
			$columns = $this->qfas("SHOW COLUMNS FROM `$table` LIKE $like") ?: [];
0 ignored issues
show
Bug introduced by
'SHOW COLUMNS FROM `'.$table.'` LIKE '.$like of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfas(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

274
			$columns = $this->qfas(/** @scrutinizer ignore-type */ "SHOW COLUMNS FROM `$table` LIKE $like") ?: [];
Loading history...
275
		} else {
276 3
			$columns = $this->qfas("SHOW COLUMNS FROM `$table`") ?: [];
277
		}
278 3
		return $columns;
279
	}
280
	/**
281
	 * @inheritdoc
282
	 */
283 1
	public function tables ($like = false) {
284 1
		if ($like) {
285 1
			$like = $this->s($like);
286 1
			return $this->qfas("SHOW TABLES FROM `$this->database` LIKE $like") ?: [];
0 ignored issues
show
Bug introduced by
'SHOW TABLES FROM `'.$th...atabase.'` LIKE '.$like of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfas(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

286
			return $this->qfas(/** @scrutinizer ignore-type */ "SHOW TABLES FROM `$this->database` LIKE $like") ?: [];
Loading history...
287
		} else {
288 1
			return $this->qfas("SHOW TABLES FROM `$this->database`") ?: [];
289
		}
290
	}
291
	/**
292
	 * @inheritdoc
293
	 */
294 29
	protected function s_internal ($string, $single_quotes_around) {
295 29
		$return = $this->instance->real_escape_string($string);
296 29
		return $single_quotes_around ? "'$return'" : $return;
297
	}
298
	/**
299
	 * @inheritdoc
300
	 */
301 1
	public function server () {
302 1
		return $this->instance->server_info;
303
	}
304
	/**
305
	 * @inheritdoc
306
	 */
307 1
	public function __destruct () {
308 1
		if ($this->connected && is_object($this->instance)) {
309 1
			$this->instance->close();
310 1
			$this->connected = false;
311
		}
312 1
	}
313
}
314