Passed
Push — master ( e79fe2...6f2ee6 )
by Ron
02:55
created

MySQLRunnableSelect::__construct()   A

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

342
				$row = FieldValueConverter::convertValues(/** @scrutinizer ignore-type */ $row, $columnDefinitions);
Loading history...
343
			}
344
			if($callback !== null) {
0 ignored issues
show
introduced by
The condition $callback !== null is always true.
Loading history...
345
				$result = $callback($row);
346
				if($result !== null) {
347
					$row = $result;
348
				}
349
			}
350
			return $row;
351
		});
352
	}
353
}
354