MySQLRunnableSelect::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 3
nc 4
nop 2
1
<?php
2
3
namespace Kir\MySQL\Databases\MySQL;
4
5
use Closure;
6
use Generator;
7
use Kir\MySQL\Builder\Helpers\DBIgnoreRow;
8
use Kir\MySQL\Builder\Helpers\FieldTypeProvider;
9
use Kir\MySQL\Builder\Helpers\FieldValueConverter;
10
use Kir\MySQL\Builder\QueryStatement;
11
use Kir\MySQL\Databases\MySQL;
12
use PDO;
13
use stdClass;
14
use Throwable;
15
use Traversable;
16
17
/**
18
 */
19
class MySQLRunnableSelect extends MySQLSelect {
20
	/** @var array<string, mixed> */
21
	private array $values = [];
22
	private bool $preserveTypes;
23
	private string $defaultClassName;
24
	private int $foundRows = 0;
25
26
	/**
27
	 * @param MySQL $db
28
	 * @param array<string, mixed> $options
29
	 */
30
	public function __construct($db, array $options = []) {
31
		parent::__construct($db);
32
		$this->preserveTypes = array_key_exists('preserve-types-default', $options) ? $options['preserve-types-default'] : false;
33
		$this->defaultClassName = array_key_exists('fetch-object-class-default', $options) ? $options['fetch-object-class-default'] : stdClass::class;
34
	}
35
36
	/**
37
	 * @inheritDoc
38
	 */
39
	public function bindValues(array $values) {
40
		$this->values = array_merge($this->values, $values);
41
42
		return $this;
43
	}
44
45
	/**
46
	 * @inheritDoc
47
	 */
48
	public function bindValue(string $key, $value) {
49
		$this->values[$key] = $value;
50
51
		return $this;
52
	}
53
54
	/**
55
	 * @inheritDoc
56
	 */
57
	public function clearValues(): self {
58
		$this->values = [];
59
60
		return $this;
61
	}
62
63
	/**
64
	 * @inheritDoc
65
	 */
66
	public function setPreserveTypes(bool $preserveTypes = true): self {
67
		$this->preserveTypes = $preserveTypes;
68
69
		return $this;
70
	}
71
72
	/**
73
	 * @inheritDoc
74
	 */
75
	public function fetchIndexedRows($callback = null): array {
76
		return $this->fetchAll($callback, PDO::FETCH_NUM);
77
	}
78
79
	/**
80
	 * @inheritDoc
81
	 */
82
	public function fetchRows($callback = null): array {
83
		return $this->fetchAll($callback, PDO::FETCH_ASSOC);
84
	}
85
86
	/**
87
	 * @inheritDoc
88
	 */
89
	public function fetchRowsLazy($callback = null) {
90
		$callback ??= static fn($row) => $row;
91
		yield from $this->fetchLazy($callback, PDO::FETCH_ASSOC);
92
	}
93
94
	/**
95
	 * @inheritDoc
96
	 */
97
	public function fetchRow($callback = null): array {
98
		$callback ??= static fn($row) => $row;
99
100
		return $this->fetch($callback, PDO::FETCH_ASSOC, null, static fn($row) => [
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch($cal...ion(...) { /* ... */ }) could return the type Kir\MySQL\Databases\MySQ...MySQL\Databases\MySQL\U which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
101
			'valid' => is_array($row),
102
			'default' => [],
103
		]);
104
	}
105
106
	/**
107
	 * @inheritDoc
108
	 */
109
	public function fetchObjects(string $className = 'stdClass', $callback = null): array {
110
		return $this->createTempStatement(function(QueryStatement $statement) use ($className, $callback) {
111
			$data = $statement->fetchAll(PDO::FETCH_CLASS, $className);
112
			if($this->preserveTypes) {
113
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
114
				$data = array_map(static fn($row) => FieldValueConverter::convertValues($row, $columnDefinitions), $data);
115
			}
116
			if($callback !== null) {
117
				return call_user_func(static function($resultData = []) use ($data, $callback) {
118
					foreach($data as $row) {
119
						$result = $callback($row);
120
						if($result !== null && !($result instanceof DBIgnoreRow)) {
121
							$resultData[] = $result;
122
						} else {
123
							$resultData[] = $row;
124
						}
125
					}
126
127
					return $resultData;
128
				});
129
			}
130
131
			return $data;
132
		});
133
	}
134
135
	/**
136
	 * @inheritDoc
137
	 */
138
	public function fetchObjectsLazy($className = null, $callback = null) {
139
		$callback ??= static fn($row) => $row;
140
		yield from $this->fetchLazy($callback, PDO::FETCH_CLASS, $className ?: $this->defaultClassName);
141
	}
142
143
	/**
144
	 * @inheritDoc
145
	 */
146
	public function fetchObject($className = null, $callback = null) {
147
		$callback ??= static fn($row) => $row;
148
149
		return $this->fetch($callback, PDO::FETCH_CLASS, $className ?: $this->defaultClassName, static fn($row) => [
150
			'valid' => is_object($row),
151
			'default' => null,
152
		]);
153
	}
154
155
	/**
156
	 * @inheritDoc
157
	 */
158
	public function fetchKeyValue($treatValueAsArray = false): array {
159
		return $this->createTempStatement(static function(QueryStatement $statement) use ($treatValueAsArray) {
160
			if($treatValueAsArray) {
161
				$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
162
				$result = [];
163
				foreach($rows as $row) {
164
					[$key] = array_values($row);
165
					$result[$key] = $row;
166
				}
167
168
				return $result;
169
			}
170
171
			return $statement->fetchAll(PDO::FETCH_KEY_PAIR);
172
		});
173
	}
174
175
	/**
176
	 * @inheritDoc
177
	 */
178
	public function fetchGroups(array $fields): array {
179
		$rows = $this->fetchRows();
180
		$result = [];
181
		foreach($rows as $row) {
182
			/** @var array<string, mixed> $tmp */
183
			$tmp = &$result;
184
			foreach($fields as $field) {
185
				$value = (string) $row[$field];
186
				if(!array_key_exists($value, $tmp)) {
187
					$tmp[$value] = [];
188
				}
189
				$tmp = &$tmp[$value];
190
			}
191
			$tmp[] = $row;
192
		}
193
194
		return $result;
195
	}
196
197
	/**
198
	 * @inheritDoc
199
	 */
200
	public function fetchArray(?callable $fn = null): array {
201
		return $this->createTempStatement(static function(QueryStatement $stmt) use ($fn) {
202
			if($fn !== null) {
203
				return $stmt->fetchAll(PDO::FETCH_FUNC, $fn);
204
			}
205
206
			return $stmt->fetchAll(PDO::FETCH_COLUMN);
207
		});
208
	}
209
210
	/**
211
	 * @inheritDoc
212
	 */
213
	public function fetchValue($default = null, ?callable $fn = null) {
214
		return $this->createTempStatement(static function(QueryStatement $stmt) use ($default, $fn) {
215
			$result = $stmt->fetchAll(PDO::FETCH_COLUMN);
216
			if($result !== false && array_key_exists(0, $result)) {
217
				return $fn !== null ? $fn($result[0]) : $result[0];
218
			}
219
220
			return $default;
221
		});
222
	}
223
224
	/**
225
	 * @inheritDoc
226
	 */
227
	public function getFoundRows(): int {
228
		return $this->foundRows;
229
	}
230
231
	/**
232
	 * @param callable $fn
233
	 * @return mixed
234
	 */
235
	private function createTempStatement(callable $fn) {
236
		$stmt = $this->createStatement();
237
		try {
238
			return $fn($stmt);
239
		} finally {
240
			$stmt->closeCursor();
241
		}
242
	}
243
244
	/**
245
	 * @return QueryStatement
246
	 */
247
	private function createStatement(): QueryStatement {
248
		$db = $this->db();
249
		$query = $this->__toString();
250
		$statement = $db->prepare($query);
251
		$statement->execute($this->values);
252
		if($this->getCalcFoundRows()) {
253
			$this->foundRows = (int) $db->query('SELECT FOUND_ROWS()')->fetchColumn();
254
		}
255
256
		return $statement;
257
	}
258
259
	/**
260
	 * @return Traversable<int, array<string, bool|float|int|string|null>>
261
	 */
262
	public function getIterator(): Traversable {
263
		yield from $this->fetchRowsLazy();
264
	}
265
266
	/**
267
	 * @template TFnReturnType of array<int|string, mixed>
268
	 * @param null|(callable(array<string, null|scalar>): (TFnReturnType|DBIgnoreRow|null|void)) $callback
0 ignored issues
show
Documentation Bug introduced by
The doc comment null|(callable(array<str...DBIgnoreRow|null|void)) at position 3 could not be parsed: Expected ')' at position 3, but found 'callable'.
Loading history...
269
	 * @param int $mode
270
	 * @param mixed $arg0
271
	 * @return ($callback is null ? array<int, ($mode is PDO::FETCH_NUM ? array<int, null|scalar> : array<string, null|scalar>)> : array<int, TFnReturnType>)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($callback at position 1 could not be parsed: Unknown type name '$callback' at position 1 in ($callback.
Loading history...
272
	 */
273
	private function fetchAll($callback = null, int $mode = 0, $arg0 = null) {
274
		return $this->createTempStatement(function(QueryStatement $statement) use ($callback, $mode, $arg0) {
275
			$statement->setFetchMode($mode, $arg0);
276
			$data = $statement->fetchAll();
277
			if($this->preserveTypes) {
278
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
279
				$data = array_map(static fn($row) => FieldValueConverter::convertValues($row, $columnDefinitions), $data);
280
			}
281
			if($callback !== null) {
282
				return call_user_func(static function($resultData = []) use ($data, $callback) {
283
					foreach($data as $row) {
284
						$result = $callback($row);
285
						if($result instanceof DBIgnoreRow) {
286
							continue;
287
						}
288
						if($result !== null) {
289
							$resultData[] = $result;
290
						} else {
291
							$resultData[] = $row;
292
						}
293
					}
294
295
					return $resultData;
296
				});
297
			}
298
299
			return $data;
300
		});
301
	}
302
303
	/**
304
	 * @template T
305
	 * @template U
306
	 * @param null|callable(T): U $callback
307
	 * @param int $mode
308
	 * @param mixed $arg0
309
	 * @return ($callback is null ? Generator<int, T> : Generator<int, U>)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($callback at position 1 could not be parsed: Unknown type name '$callback' at position 1 in ($callback.
Loading history...
310
	 */
311
	private function fetchLazy($callback = null, int $mode = PDO::FETCH_ASSOC, $arg0 = null): Generator {
312
		$statement = $this->createStatement();
313
		$statement->setFetchMode($mode, $arg0);
314
		try {
315
			while($row = $statement->fetch()) {
316
				/** @var T $row */
317
//				if($this->preserveTypes) {
318
//					$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
319
//					$row = FieldValueConverter::convertValues($row, $columnDefinitions);
320
//				}
321
				$result = $callback($row);
322
				if($result instanceof DBIgnoreRow) {
323
					// Skip row in this case
324
					continue;
325
				}
326
				if($result !== null) {
327
					yield $result;
328
				} else {
329
					yield $row;
330
				}
331
			}
332
		} finally {
333
			try {
334
				$statement->closeCursor();
335
			} catch(Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
336
			}
337
		}
338
	}
339
340
	/**
341
	 * @template T
342
	 * @template U
343
	 * @param callable(T): U $callback
344
	 * @param int $mode
345
	 * @param mixed $arg0
346
	 * @param Closure|null $resultValidator
347
	 * @return T|U|array<string, mixed>
0 ignored issues
show
Bug introduced by
The type Kir\MySQL\Databases\MySQL\U was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type Kir\MySQL\Databases\MySQL\T was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
348
	 */
349
	private function fetch($callback, int $mode = PDO::FETCH_ASSOC, $arg0 = null, ?Closure $resultValidator = null) {
350
		return $this->createTempStatement(function(QueryStatement $statement) use ($callback, $mode, $arg0, $resultValidator) {
351
			$statement->setFetchMode($mode, $arg0);
352
			$row = $statement->fetch();
353
			$result = $resultValidator === null ? ['valid' => true] : $resultValidator($row);
354
			if(!$result['valid']) {
355
				return $result['default'];
356
			}
357
			if($this->preserveTypes) {
358
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
359
				$row = FieldValueConverter::convertValues($row, $columnDefinitions);
0 ignored issues
show
Bug introduced by
$row of type Kir\MySQL\Builder\T is incompatible with the type array expected by parameter $row of Kir\MySQL\Builder\Helper...verter::convertValues(). ( Ignorable by Annotation )

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

359
				$row = FieldValueConverter::convertValues(/** @scrutinizer ignore-type */ $row, $columnDefinitions);
Loading history...
360
			}
361
			if($callback !== null) {
0 ignored issues
show
introduced by
The condition $callback !== null is always true.
Loading history...
362
				$result = $callback($row);
363
				if($result !== null) {
364
					$row = $result;
365
				}
366
			}
367
368
			return $row;
369
		});
370
	}
371
}
372