Insert::clearValues()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 16
c 0
b 0
f 0
rs 9.9666
cc 4
nc 4
nop 1
1
<?php
2
3
namespace Kir\MySQL\Builder;
4
5
use Kir\MySQL\Builder\Internal\Types;
6
use Kir\MySQL\Tools\AliasReplacer;
7
use RuntimeException;
8
use Traversable;
9
use UnexpectedValueException;
10
11
/**
12
 * @phpstan-import-type DBParameterValueType from Types
13
 */
14
abstract class Insert extends InsertUpdateStatement {
15
	/** @var array<string|int, DBParameterValueType> */
16
	private array $fields = [];
17
	/** @var array<string|int, DBParameterValueType> */
18
	private array $update = [];
19
	private ?string $table = null;
20
	private ?string $keyField = null;
21
	private bool $ignore = false;
22
	private ?Select $from = null;
23
24
	/**
25
	 * @param string $table
26
	 * @return $this
27
	 */
28
	public function into(string $table) {
29
		$this->table = $table;
30
31
		return $this;
32
	}
33
34
	/**
35
	 * @param bool $value
36
	 * @return $this
37
	 */
38
	public function setIgnore(bool $value = true) {
39
		$this->ignore = $value;
40
41
		return $this;
42
	}
43
44
	/**
45
	 * Legt den Primaerschluessel fest.
46
	 * Wenn bei einem Insert der Primaerschluessel mitgegeben wird, dann wird dieser statt der LastInsertId
47
	 * zurueckgegeben
48
	 *
49
	 * @param string $field
50
	 * @return $this
51
	 */
52
	public function setKey(string $field) {
53
		$this->keyField = $field;
54
55
		return $this;
56
	}
57
58
	/**
59
	 * @param string $field
60
	 * @param DBParameterValueType $value
0 ignored issues
show
Bug introduced by
The type Kir\MySQL\Builder\DBParameterValueType 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...
61
	 * @return $this
62
	 */
63
	public function add(string $field, $value) {
64
		$this->fields = $this->addTo($this->fields, $field, $value);
65
66
		return $this;
67
	}
68
69
	/**
70
	 * @param string $field
71
	 * @param DBParameterValueType $value
72
	 * @return $this
73
	 */
74
	public function update(string $field, $value) {
75
		$this->update = $this->addTo($this->update, $field, $value);
76
77
		return $this;
78
	}
79
80
	/**
81
	 * @param string $field
82
	 * @param DBParameterValueType $value
83
	 * @return $this
84
	 */
85
	public function addOrUpdate(string $field, $value) {
86
		$this->add($field, $value);
87
		$this->update($field, $value);
88
89
		return $this;
90
	}
91
92
	/**
93
	 * @param string $str
94
	 * @param DBParameterValueType ...$args
95
	 * @return $this
96
	 */
97
	public function addExpr(string $str, ...$args) {
98
		if(count($args) > 0) {
99
			$this->fields[] = func_get_args();
100
		} else {
101
			$this->fields[] = $str;
102
		}
103
104
		return $this;
105
	}
106
107
	/**
108
	 * @param string $str
109
	 * @param DBParameterValueType ...$args
110
	 * @return $this
111
	 */
112
	public function updateExpr(string $str, ...$args) {
113
		if(count($args) > 0) {
114
			$this->update[] = func_get_args();
115
		} else {
116
			$this->update[] = $str;
117
		}
118
119
		return $this;
120
	}
121
122
	/**
123
	 * @param string $expr
124
	 * @param DBParameterValueType ...$args
125
	 * @return $this
126
	 */
127
	public function addOrUpdateExpr(string $expr, ...$args) {
128
		if(count($args) > 0) {
129
			$this->fields[] = func_get_args();
130
			$this->update[] = func_get_args();
131
		} else {
132
			$this->fields[] = $expr;
133
			$this->update[] = $expr;
134
		}
135
136
		return $this;
137
	}
138
139
	/**
140
	 * @param array<string, DBParameterValueType> $data
141
	 * @param null|string[] $mask
142
	 * @param null|string[] $excludeFields
143
	 * @return $this
144
	 */
145
	public function addAll(array $data, ?array $mask = null, ?array $excludeFields = null) {
146
		$this->addAllTo($data, $mask, $excludeFields, function($field, $value) {
147
			$this->add($field, $value);
148
		});
149
150
		return $this;
151
	}
152
153
	/**
154
	 * @param array<string, DBParameterValueType> $data
155
	 * @param null|string[] $mask
156
	 * @param null|string[] $excludeFields
157
	 * @return $this
158
	 */
159
	public function updateAll(array $data, ?array $mask = null, ?array $excludeFields = null) {
160
		$this->addAllTo($data, $mask, $excludeFields, function($field, $value) {
161
			if($field !== $this->keyField) {
162
				$this->update($field, $value);
163
			}
164
		});
165
166
		return $this;
167
	}
168
169
	/**
170
	 * @param array<string, DBParameterValueType> $data
171
	 * @param null|string[] $mask
172
	 * @param array<int, string>|null $excludeFields
173
	 * @return $this
174
	 */
175
	public function addOrUpdateAll(array $data, ?array $mask = null, ?array $excludeFields = null) {
176
		$this->addAll($data, $mask, $excludeFields);
177
		$this->updateAll($data, $mask, $excludeFields);
178
179
		return $this;
180
	}
181
182
	/**
183
	 * @param Select $select
184
	 * @return $this
185
	 */
186
	public function from(Select $select) {
187
		$this->from = $select;
188
189
		return $this;
190
	}
191
192
	/**
193
	 * @param iterable<int, array<string, mixed>>|Traversable<int, array<string, mixed>> $rows
194
	 * @return int[] Insert IDs
195
	 */
196
	abstract public function insertRows(iterable $rows);
197
198
	/**
199
	 * @param array<string, mixed> $params
200
	 * @return int
201
	 */
202
	abstract public function run(array $params = []): int;
203
204
	/**
205
	 * @return string
206
	 */
207
	public function __toString(): string {
208
		if($this->table === null) {
209
			throw new RuntimeException('Specify a table-name');
210
		}
211
212
		$tableName = (new AliasReplacer($this->db()->getAliasRegistry()))->replace($this->table);
213
214
		$queryArr = [];
215
		$ignoreStr = $this->ignore ? ' IGNORE' : '';
216
		$queryArr[] = "INSERT{$ignoreStr} INTO\n\t{$tableName}\n";
217
218
		if($this->from !== null) {
219
			$fields = $this->from->getFields();
220
			$queryArr[] = sprintf("\t(%s)\n", implode(', ', array_keys($fields)));
221
			$queryArr[] = $this->from;
222
		} else {
223
			$fields = $this->fields;
224
			$insertData = $this->buildFieldList($fields);
225
			if(!count($insertData)) {
226
				throw new RuntimeException('No field-data found');
227
			}
228
			$queryArr[] = sprintf("SET\n%s\n", implode(",\n", $insertData));
229
		}
230
231
		$updateData = $this->buildUpdate();
232
		if($updateData) {
233
			$queryArr[] = "{$updateData}\n";
234
		}
235
236
		return implode('', $queryArr);
237
	}
238
239
	/**
240
	 * @param array<string|int, mixed> $fields
241
	 * @param string $field
242
	 * @param DBParameterValueType $value
243
	 * @return array<string|int, mixed>
244
	 */
245
	private function addTo(array $fields, string $field, $value): array {
246
		if(!$this->isFieldNameValid($field)) {
247
			throw new UnexpectedValueException('Field name is invalid');
248
		}
249
		$sqlField = $field;
250
		$sqlValue = $this->db()->quote($value);
251
		$fields[$sqlField] = $sqlValue;
252
253
		return $fields;
254
	}
255
256
	/**
257
	 * @param array<string, mixed> $data
258
	 * @param string[]|null $mask
259
	 * @param string[]|null $excludeFields
260
	 * @param callable(string, mixed): void $fn
261
	 */
262
	private function addAllTo(array $data, ?array $mask, ?array $excludeFields, $fn): void {
263
		if($mask !== null) {
0 ignored issues
show
introduced by
The condition $mask !== null is always true.
Loading history...
264
			$data = array_intersect_key($data, array_combine($mask, $mask));
265
		}
266
		if($excludeFields !== null) {
0 ignored issues
show
introduced by
The condition $excludeFields !== null is always true.
Loading history...
267
			foreach($excludeFields as $excludeField) {
268
				if(array_key_exists($excludeField, $data)) {
269
					unset($data[$excludeField]);
270
				}
271
			}
272
		}
273
		$data = $this->clearValues($data);
274
		foreach($data as $field => $value) {
275
			$fn($field, $value);
276
		}
277
	}
278
279
	/**
280
	 * @return string
281
	 */
282
	private function buildUpdate(): string {
283
		$queryArr = [];
284
		if(!empty($this->update)) {
285
			$queryArr[] = "ON DUPLICATE KEY UPDATE\n";
286
			$updateArr = [];
287
			if($this->keyField !== null) {
288
				$updateArr[] = "\t`{$this->keyField}` = LAST_INSERT_ID({$this->keyField})";
289
			}
290
			$updateArr = $this->buildFieldList($this->update, $updateArr);
291
292
			$queryArr[] = implode(",\n", $updateArr);
293
		}
294
295
		return implode('', $queryArr);
296
	}
297
298
	/**
299
	 * @param string $fieldName
300
	 * @return bool
301
	 */
302
	private function isFieldNameValid(string $fieldName): bool {
303
		return !(is_numeric($fieldName) || !is_scalar($fieldName));
304
	}
305
306
	/**
307
	 * @param array<string, mixed> $values
308
	 * @return array<string, mixed>
309
	 */
310
	private function clearValues(array $values): array {
311
		if(!count($values)) {
312
			return [];
313
		}
314
315
		$tableName = (new AliasReplacer($this->db()->getAliasRegistry()))->replace($this->table);
0 ignored issues
show
Bug introduced by
It seems like $this->table can also be of type null; however, parameter $name of Kir\MySQL\Tools\AliasReplacer::replace() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

315
		$tableName = (new AliasReplacer($this->db()->getAliasRegistry()))->replace(/** @scrutinizer ignore-type */ $this->table);
Loading history...
316
		$fields = $this->db()->getTableFields($tableName);
317
		$result = [];
318
319
		foreach($values as $fieldName => $fieldValue) {
320
			if(in_array($fieldName, $fields)) {
321
				$result[$fieldName] = $fieldValue;
322
			}
323
		}
324
325
		return $result;
326
	}
327
}
328