Issues (83)

src/Builder/Insert.php (4 issues)

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

300
		$tableName = (new AliasReplacer($this->db()->getAliasRegistry()))->replace(/** @scrutinizer ignore-type */ $this->table);
Loading history...
301
		$fields = $this->db()->getTableFields($tableName);
302
		$result = [];
303
304
		foreach($values as $fieldName => $fieldValue) {
305
			if(in_array($fieldName, $fields)) {
306
				$result[$fieldName] = $fieldValue;
307
			}
308
		}
309
310
		return $result;
311
	}
312
}
313