1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace BenTools\Where\InsertQuery; |
5
|
|
|
|
6
|
|
|
use function BenTools\FlattenIterator\flatten; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Class InsertQueryBuilder |
10
|
|
|
* |
11
|
|
|
* @property $mainKeyword |
12
|
|
|
* @property $flags |
13
|
|
|
* @property $values |
14
|
|
|
* @property $columns |
15
|
|
|
* @property $table |
16
|
|
|
* @property $onDuplicate |
17
|
|
|
* @property $end |
18
|
|
|
* @property $escape |
19
|
|
|
*/ |
20
|
|
|
final class InsertQueryBuilder |
21
|
|
|
{ |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var string |
25
|
|
|
*/ |
26
|
|
|
private $mainKeyword = 'INSERT'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var array |
30
|
|
|
*/ |
31
|
|
|
private $flags = []; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
private $values = []; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
private $columns; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
private $table; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
private $onDuplicate; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var string |
55
|
|
|
*/ |
56
|
|
|
private $end = ';'; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var string |
60
|
|
|
*/ |
61
|
|
|
private $escape; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param array[] ...$values |
65
|
|
|
* @return InsertQueryBuilder |
66
|
|
|
* @throws \InvalidArgumentException |
67
|
|
|
*/ |
68
|
|
|
public static function load(array ...$values): self |
69
|
|
|
{ |
70
|
|
|
if (0 === count($values)) { |
71
|
|
|
throw new \InvalidArgumentException("At least 1 value is needed."); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$query = new self; |
75
|
|
|
foreach ($values as $value) { |
76
|
|
|
$query->values[] = $query->validateValue($value); |
77
|
|
|
} |
78
|
|
|
return $query; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param string $keyword |
83
|
|
|
* @return InsertQueryBuilder |
84
|
|
|
*/ |
85
|
|
|
public function withMainKeyword(string $keyword): self |
86
|
|
|
{ |
87
|
|
|
$clone = clone $this; |
88
|
|
|
$clone->mainKeyword = $keyword; |
89
|
|
|
return $clone; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @param string $table |
94
|
|
|
* @param string[] ...$columns |
95
|
|
|
* @return InsertQueryBuilder |
96
|
|
|
*/ |
97
|
|
|
public function into(string $table, string ...$columns): self |
98
|
|
|
{ |
99
|
|
|
$clone = clone $this; |
100
|
|
|
$clone->table = $table; |
101
|
|
|
$clone->columns = [] === $columns ? null : $columns; |
102
|
|
|
return $clone; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @param string[] ...$flags |
108
|
|
|
* @return InsertQueryBuilder |
109
|
|
|
*/ |
110
|
|
|
public function withFlags(string ...$flags): self |
111
|
|
|
{ |
112
|
|
|
$clone = clone $this; |
113
|
|
|
$clone->flags = $flags; |
114
|
|
|
return $clone; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @param string[] ...$flags |
119
|
|
|
* @return InsertQueryBuilder |
120
|
|
|
*/ |
121
|
|
|
public function withAddedFlags(string ...$flags): self |
122
|
|
|
{ |
123
|
|
|
$clone = clone $this; |
124
|
|
|
$existingFlags = array_map('strtoupper', $clone->flags); |
125
|
|
|
foreach ($flags as $flag) { |
126
|
|
|
if (!in_array(strtoupper($flag), $existingFlags, true)) { |
127
|
|
|
$clone->flags[] = $flag; |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
return $clone; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @param string|null $escape |
135
|
|
|
* @return InsertQueryBuilder |
136
|
|
|
*/ |
137
|
|
|
public function withEscaper(string $escape = null): self |
138
|
|
|
{ |
139
|
|
|
$clone = clone $this; |
140
|
|
|
$clone->escape = $escape; |
141
|
|
|
return $clone; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @param array $updateConditions |
146
|
|
|
* @return InsertQueryBuilder |
147
|
|
|
*/ |
148
|
|
|
public function onDuplicateKeyUpdate(array $updateConditions = null): self |
149
|
|
|
{ |
150
|
|
|
$clone = clone $this; |
151
|
|
|
$clone->onDuplicate = $updateConditions; |
|
|
|
|
152
|
|
|
return $clone; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @param null $end |
157
|
|
|
* @return InsertQueryBuilder |
158
|
|
|
*/ |
159
|
|
|
public function end($end = null): self |
160
|
|
|
{ |
161
|
|
|
$clone = clone $this; |
162
|
|
|
$clone->end = $end; |
163
|
|
|
return $clone; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @param array $value |
168
|
|
|
* @return array |
169
|
|
|
* @throws \InvalidArgumentException |
170
|
|
|
*/ |
171
|
|
|
private function validateValue(array &$value): array |
172
|
|
|
{ |
173
|
|
|
if (empty($this->values)) { |
174
|
|
|
return $value; |
175
|
|
|
} |
176
|
|
|
$keys = array_keys($this->values[0]); |
177
|
|
|
$valueKeys = array_keys($value); |
178
|
|
|
if ($valueKeys !== $keys) { |
179
|
|
|
if (count($keys) !== count($valueKeys)) { |
180
|
|
|
throw new \InvalidArgumentException("Invalid value."); |
181
|
|
|
} |
182
|
|
|
uksort($value, function ($key1, $key2) use ($keys) { |
183
|
|
|
return array_search($key1, $keys) <=> array_search($key2, $keys); |
184
|
|
|
}); |
185
|
|
|
$valueKeys = array_keys($value); |
186
|
|
|
if ($valueKeys !== $keys) { |
187
|
|
|
throw new \InvalidArgumentException("Invalid value."); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
return $value; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* @param string $value |
195
|
|
|
* @return string |
196
|
|
|
*/ |
197
|
|
|
public function escape(string $value): string |
198
|
|
|
{ |
199
|
|
|
return null === $this->escape ? $value : $this->escape . $value . $this->escape; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
public function __toString(): string |
206
|
|
|
{ |
207
|
|
|
return InsertQueryStringifier::stringify($this); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Split into multiple INSERT statements. |
212
|
|
|
* |
213
|
|
|
* @param int $max |
214
|
|
|
* @return iterable|self[] |
215
|
|
|
*/ |
216
|
|
|
public function split(int $max): iterable |
217
|
|
|
{ |
218
|
|
|
$pos = 0; |
219
|
|
|
$total = count($this->values); |
220
|
|
|
while ($pos < $total) { |
221
|
|
|
$clone = clone $this; |
222
|
|
|
$clone->values = array_slice($this->values, $pos, $max); |
223
|
|
|
$pos += $max; |
224
|
|
|
yield $clone; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @return array |
230
|
|
|
*/ |
231
|
|
|
public function getValues(): array |
232
|
|
|
{ |
233
|
|
|
$generator = function (array $values, array $columns) { |
234
|
|
|
foreach ($values as $value) { |
235
|
|
|
$valueKeys = array_keys($value); |
236
|
|
|
if ($valueKeys !== $columns) { |
237
|
|
|
$value = array_intersect_key($value, array_combine($columns, array_fill(0, count($columns), null))); |
238
|
|
|
$valueKeys = array_keys($value); |
239
|
|
|
if ($valueKeys !== $columns) { |
240
|
|
|
uksort($value, function ($key1, $key2) use ($columns) { |
241
|
|
|
return array_search($key1, $columns) <=> array_search($key2, $columns); |
242
|
|
|
}); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
yield $value; |
246
|
|
|
} |
247
|
|
|
}; |
248
|
|
|
return flatten($generator($this->values, $this->getColumns()))->asArray(); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @return array |
253
|
|
|
*/ |
254
|
|
|
public function getColumns() |
255
|
|
|
{ |
256
|
|
|
return $this->columns ?? array_keys($this->values[0] ?? []); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Read-only properties. |
261
|
|
|
* |
262
|
|
|
* @param $property |
263
|
|
|
* @return mixed |
264
|
|
|
* @throws \InvalidArgumentException |
265
|
|
|
*/ |
266
|
|
|
public function __get($property) |
267
|
|
|
{ |
268
|
|
|
if (!property_exists($this, $property)) { |
269
|
|
|
throw new \InvalidArgumentException(sprintf('Property %s::$%s does not exist.', __CLASS__, $property)); |
270
|
|
|
} |
271
|
|
|
return $this->{$property}; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.
To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.
The function can be called with either null or an array for the parameter
$needle
but will only accept an array as$haystack
.