1
|
|
|
<?php |
2
|
|
|
namespace DBFaker; |
3
|
|
|
|
4
|
|
|
use DBFaker\Exceptions\DBFakerException; |
5
|
|
|
use DBFaker\Generators\CompoundColumnGenerator; |
6
|
|
|
use DBFaker\Generators\FakeDataGeneratorInterface; |
7
|
|
|
use DBFaker\Generators\ForeignKeyColumnGenerator; |
8
|
|
|
use DBFaker\Generators\GeneratorFactory; |
|
|
|
|
9
|
|
|
use DBFaker\Generators\GeneratorFinder; |
10
|
|
|
use DBFaker\Helpers\DBFakerSchemaManager; |
11
|
|
|
use DBFaker\Helpers\PrimaryKeyRegistry; |
12
|
|
|
use DBFaker\Helpers\SchemaHelper; |
13
|
|
|
use Doctrine\DBAL\Connection; |
14
|
|
|
use Doctrine\DBAL\Schema\AbstractSchemaManager; |
15
|
|
|
use Doctrine\DBAL\Schema\Column; |
16
|
|
|
use Doctrine\DBAL\Schema\ForeignKeyConstraint; |
17
|
|
|
use Doctrine\DBAL\Schema\Index; |
18
|
|
|
use Doctrine\DBAL\Schema\Table; |
19
|
|
|
use Doctrine\DBAL\Types\Type; |
20
|
|
|
use Mouf\Utils\Log\Psr\ErrorLogLogger; |
21
|
|
|
use Psr\Log\LoggerInterface; |
22
|
|
|
|
23
|
|
|
class DBFaker |
24
|
|
|
{ |
25
|
|
|
public const MAX_ITERATIONS_FOR_UNIQUE_VALUE = 1000; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var Connection |
29
|
|
|
*/ |
30
|
|
|
private $connection; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var AbstractSchemaManager |
34
|
|
|
*/ |
35
|
|
|
private $schemaManager; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var GeneratorFinder |
39
|
|
|
*/ |
40
|
|
|
private $generatorFinder; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var LoggerInterface |
44
|
|
|
*/ |
45
|
|
|
private $log; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var array |
49
|
|
|
*/ |
50
|
|
|
private $fakeTableRowNumbers = []; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var PrimaryKeyRegistry[] |
54
|
|
|
*/ |
55
|
|
|
private $primaryKeyRegistries = []; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var int |
59
|
|
|
*/ |
60
|
|
|
private $nullProbability = 10; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var SchemaHelper |
64
|
|
|
*/ |
65
|
|
|
private $schemaHelper; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var CompoundColumnGenerator[] |
69
|
|
|
*/ |
70
|
|
|
private $compoundColumnGenerators; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @var ForeignKeyColumnGenerator[] |
74
|
|
|
*/ |
75
|
|
|
private $fkColumnsGenerators; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @var string[] |
79
|
|
|
*/ |
80
|
|
|
private $handledFKColumns = []; |
|
|
|
|
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @var DBFakerSchemaManager |
84
|
|
|
*/ |
85
|
|
|
private $fakerManagerHelper; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* DBFaker constructor. |
89
|
|
|
* @param Connection $connection |
90
|
|
|
* @param GeneratorFinder $generatorFinder |
91
|
|
|
* @param LoggerInterface $log |
92
|
|
|
* @internal param SchemaAnalyzer $schemaAnalyzer |
93
|
|
|
*/ |
94
|
|
|
public function __construct(Connection $connection, GeneratorFinder $generatorFinder, LoggerInterface $log = null) |
95
|
|
|
{ |
96
|
|
|
$this->connection = $connection; |
97
|
|
|
$this->generatorFinder = $generatorFinder; |
98
|
|
|
$this->log = $log ?? new ErrorLogLogger(); |
99
|
|
|
$schema = $this->connection->getSchemaManager()->createSchema(); |
100
|
|
|
$this->schemaManager = $this->connection->getSchemaManager(); |
101
|
|
|
$this->schemaHelper = new SchemaHelper($schema); |
102
|
|
|
$this->fakerManagerHelper = new DBFakerSchemaManager($this->schemaManager); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Main function : does all the job |
107
|
|
|
* @throws \DBFaker\Exceptions\UnsupportedDataTypeException |
108
|
|
|
* @throws \DBFaker\Exceptions\SchemaLogicException |
109
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
110
|
|
|
* @throws \DBFaker\Exceptions\DBFakerException |
111
|
|
|
* @throws \Doctrine\DBAL\DBALException |
112
|
|
|
*/ |
113
|
|
|
public function fakeDB() : void |
114
|
|
|
{ |
115
|
|
|
set_time_limit(0);//Import may take a looooooong time :) |
116
|
|
|
$data = $this->generateFakeData(); |
117
|
|
|
$extensionContraints = $this->getExtensionConstraints(); |
118
|
|
|
$foreignKeys = $this->dropForeignKeys(); |
119
|
|
|
$multipleUniqueContraints = $this->dropMultipleUniqueContraints(); |
120
|
|
|
$this->insertFakeData($data, $extensionContraints, $multipleUniqueContraints, $foreignKeys); |
121
|
|
|
$this->restoreForeignKeys($foreignKeys); |
122
|
|
|
$this->restoreMultipleUniqueContraints($multipleUniqueContraints); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Generates the fake data for specified tables |
127
|
|
|
* @return mixed[] |
128
|
|
|
* @throws \DBFaker\Exceptions\UnsupportedDataTypeException |
129
|
|
|
* @throws \DBFaker\Exceptions\PrimaryKeyColumnMismatchException |
130
|
|
|
* @throws \Doctrine\DBAL\DBALException |
131
|
|
|
* @throws \Exception |
132
|
|
|
*/ |
133
|
|
|
public function generateFakeData() : array |
134
|
|
|
{ |
135
|
|
|
$this->log->info("Step 1 : Generating data ..."); |
136
|
|
|
|
137
|
|
|
$data = []; |
138
|
|
|
foreach ($this->fakeTableRowNumbers as $tableName => $nbLines) { |
139
|
|
|
$table = $this->schemaManager->listTableDetails($tableName); |
140
|
|
|
$data[$table->getName()] = $this->getFakeDataForTable($table, $nbLines); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
return $data; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param Table $table the table for which fake data will be generated |
148
|
|
|
* @param int $nbLines : the number of lines to generate |
149
|
|
|
* @return mixed[] |
150
|
|
|
* @throws \DBFaker\Exceptions\UnsupportedDataTypeException |
151
|
|
|
* @throws \DBFaker\Exceptions\PrimaryKeyColumnMismatchException |
152
|
|
|
* @throws \Doctrine\DBAL\DBALException |
153
|
|
|
* @throws \Exception |
154
|
|
|
*/ |
155
|
|
|
private function getFakeDataForTable(Table $table, int $nbLines) : array |
156
|
|
|
{ |
157
|
|
|
$data = []; |
158
|
|
|
for ($i = 0; $i < $nbLines; $i++) { |
159
|
|
|
$this->log->info('Step 1 : table ' . $table->getName() . "$i / " . $nbLines); |
160
|
|
|
$row = []; |
161
|
|
|
foreach ($table->getColumns() as $column) { |
162
|
|
|
//IF column is a PK and Autoincrement then values will be set to null, let the database generate them |
163
|
|
|
if ($this->schemaHelper->isPrimaryKeyColumn($table, $column) && $column->getAutoincrement()) { |
164
|
|
|
$value = null; |
165
|
|
|
} else { |
166
|
|
|
//Other data will be Faked depending of column's type and attributes. FKs to, but their values wil be overridden. |
167
|
|
|
if (!$column->getNotnull() && $this->nullProbabilityOccured()) { |
168
|
|
|
$value = null; |
169
|
|
|
} else { |
170
|
|
|
$generator = $this->getSimpleColumnGenerator($table, $column); |
171
|
|
|
$value = $generator(); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
$row[$column->getName()] = $value; |
175
|
|
|
} |
176
|
|
|
$data[] = $row; |
177
|
|
|
} |
178
|
|
|
return $data; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Inserts the data. This is done in 2 steps : |
183
|
|
|
* - first insert data for all lines / columns. FKs will be assigned values that only match there type. This step allows to create PK values for second step. |
184
|
|
|
* - second turn will update FKs to set random PK values from the previously generated lines. |
185
|
|
|
* @param array[] $data |
186
|
|
|
* @param ForeignKeyConstraint[] $extensionContraints |
187
|
|
|
* @param array<string, Index[]> $multipleUniqueContraints |
188
|
|
|
* @param array<string, ForeignKeyConstraint[]> $foreignKeys |
189
|
|
|
* @throws \Doctrine\DBAL\DBALException |
190
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
191
|
|
|
*/ |
192
|
|
|
private function insertFakeData(array $data, array $extensionContraints, array $multipleUniqueContraints, array $foreignKeys) : void |
193
|
|
|
{ |
194
|
|
|
//1 - First insert data with no FKs, and null PKs. This will generate primary keys |
195
|
|
|
$this->log->info('Step 3.1 : Insert simple data ...'); |
196
|
|
|
$this->insertWithoutFksAndUniqueIndexes($data); |
197
|
|
|
|
198
|
|
|
//2 - loop on multiple unique index constraints (that may include FKs) |
199
|
|
|
$this->log->info('Step 3.2 : Update Multiple Unique Indexed Columns'); |
200
|
|
|
$handledColumns = $this->updateExtensionContraints($extensionContraints); |
201
|
|
|
|
202
|
|
|
//2 - loop on multiple unique index constraints (that may include FKs) |
203
|
|
|
$this->log->info('Step 3.3 : Update Multiple Unique Indexed Columns'); |
204
|
|
|
$handledColumns = $this->updateMultipleUniqueIndexedColumns($multipleUniqueContraints, $handledColumns); |
205
|
|
|
|
206
|
|
|
//3 - loop again to set FKs now that all PK have been loaded |
207
|
|
|
$this->log->info('Step 3.4 : Update Remaining ForeignKeys'); |
208
|
|
|
$this->updateRemainingForeignKeys($foreignKeys, $handledColumns); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Inserts base data : |
213
|
|
|
* - AutoIncrement PKs will be generated and stored |
214
|
|
|
* - ForeignKey and Multiple Unique Indexes are ignored, because we need self-generated PK values |
215
|
|
|
* @param array[] $data |
216
|
|
|
* @throws \Doctrine\DBAL\DBALException |
217
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
218
|
|
|
*/ |
219
|
|
|
private function insertWithoutFksAndUniqueIndexes(array $data): void |
220
|
|
|
{ |
221
|
|
|
$plateform = $this->connection->getDatabasePlatform(); |
222
|
|
|
foreach ($data as $tableName => $rows) { |
223
|
|
|
$table = $this->schemaManager->listTableDetails($tableName); |
224
|
|
|
|
225
|
|
|
//initiate column types for insert : only get the first array to retrieve column names |
226
|
|
|
$types = []; |
227
|
|
|
$first = reset($rows); |
228
|
|
|
if ($first) { |
229
|
|
|
foreach ($first as $columnName => $value) { |
230
|
|
|
/** @var Column $column */ |
231
|
|
|
$column = $table->getColumn($columnName); |
232
|
|
|
$types[] = $column->getType()->getBindingType(); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
//insert faked data |
237
|
|
|
$cnt = count($rows); |
238
|
|
|
foreach ($rows as $index => $row) { |
239
|
|
|
$dbRow = []; |
240
|
|
|
foreach ($row as $columnName => $value) { |
241
|
|
|
$column = $table->getColumn($columnName); |
242
|
|
|
$newVal = $column->getType()->convertToDatabaseValue($value, $plateform); |
243
|
|
|
$dbRow[$column->getQuotedName($this->connection->getDatabasePlatform())] = $newVal; |
244
|
|
|
} |
245
|
|
|
$this->log->info("Step 3.1 : Inserted $index of $cnt in $tableName"); |
246
|
|
|
$this->connection->insert($table->getName(), $dbRow, $types); |
247
|
|
|
} |
248
|
|
|
//if autoincrement, add the new ID to the PKRegistry |
249
|
|
|
if ($table->hasPrimaryKey()) { |
250
|
|
|
$pkColumnName = $table->getPrimaryKeyColumns()[0]; |
251
|
|
|
$pkColumn = $table->getColumn($pkColumnName); |
252
|
|
|
if ($pkColumn->getAutoincrement() && $this->schemaHelper->isPrimaryKeyColumn($table, $pkColumn)) { |
253
|
|
|
$this->getPkRegistry($table)->addValue([$pkColumnName => $this->connection->lastInsertId()]); |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @param Table $table |
261
|
|
|
* @return PrimaryKeyRegistry |
262
|
|
|
* @throws \Doctrine\DBAL\DBALException |
263
|
|
|
* @throws \DBFaker\Exceptions\SchemaLogicException |
264
|
|
|
*/ |
265
|
|
|
public function getPkRegistry(Table $table, bool $isSelfReferencing = false) : PrimaryKeyRegistry |
266
|
|
|
{ |
267
|
|
|
$index = $table->getName().($isSelfReferencing ? 'dbfacker_self_referencing' :''); |
268
|
|
|
if (!isset($this->primaryKeyRegistries[$index])) { |
269
|
|
|
$this->primaryKeyRegistries[$index] = new PrimaryKeyRegistry($this->connection, $table, $this->schemaHelper, $isSelfReferencing); |
270
|
|
|
} |
271
|
|
|
return $this->primaryKeyRegistries[$index]; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* @return bool : if null value should be generated |
276
|
|
|
* @throws \Exception |
277
|
|
|
*/ |
278
|
|
|
private function nullProbabilityOccured() : bool |
279
|
|
|
{ |
280
|
|
|
return random_int(0, 100) < $this->nullProbability; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Sets the number of lines that should be generated for each table |
285
|
|
|
* @param int[] $fakeTableRowNumbers : associative array - Key is the name of the table, and value the number of lines to the faked |
286
|
|
|
*/ |
287
|
|
|
public function setFakeTableRowNumbers(array $fakeTableRowNumbers) : void |
288
|
|
|
{ |
289
|
|
|
$this->fakeTableRowNumbers = $fakeTableRowNumbers; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Sets the null probability : chance to generate a null value for nullable columns (between 0 and 100, default is 10) |
294
|
|
|
* @param int $nullProbability |
295
|
|
|
*/ |
296
|
|
|
public function setNullProbability(int $nullProbability) : void |
297
|
|
|
{ |
298
|
|
|
$this->nullProbability = $nullProbability; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Drop all foreign keys because it is too complicated to solve the table reference graph in order to generate data in the right order. |
303
|
|
|
* FKs are stored to be recreated at the end |
304
|
|
|
* @throws \Doctrine\DBAL\DBALException |
305
|
|
|
* @throws \DBFaker\Exceptions\SchemaLogicException |
306
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
307
|
|
|
* @return mixed[] |
308
|
|
|
*/ |
309
|
|
|
private function dropForeignKeys() : array |
310
|
|
|
{ |
311
|
|
|
$this->log->info('Step 2.1 : Drop FKs ...'); |
312
|
|
|
$foreignKeys = []; |
313
|
|
|
$tables = $this->schemaManager->listTables(); |
314
|
|
|
foreach ($tables as $table) { |
315
|
|
|
foreach ($table->getForeignKeys() as $fk) { |
316
|
|
|
$foreignTable = $this->schemaManager->listTableDetails($fk->getForeignTableName()); |
317
|
|
|
foreach ($fk->getColumns() as $localColumnName) { |
318
|
|
|
$localColumn = $table->getColumn($localColumnName); |
319
|
|
|
$selfReferencing = $fk->getForeignTableName() === $table->getName(); |
320
|
|
|
$fkValueGenerator = new ForeignKeyColumnGenerator($table, $localColumn, $this->getPkRegistry($foreignTable, $selfReferencing), $fk, $this->fakerManagerHelper, $this->schemaHelper); |
321
|
|
|
$this->fkColumnsGenerators[$table->getName() . '.' . $localColumnName] = $fkValueGenerator; |
322
|
|
|
} |
323
|
|
|
$foreignKeys[$table->getName()][] = $fk; |
324
|
|
|
$this->schemaManager->dropForeignKey($fk, $table); |
325
|
|
|
} |
326
|
|
|
} |
327
|
|
|
return $foreignKeys; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Restore the foreign keys based on the ForeignKeys store built when calling dropForeignKeys() |
332
|
|
|
* @param mixed $foreignKeys |
333
|
|
|
*/ |
334
|
|
|
private function restoreForeignKeys($foreignKeys) : void |
335
|
|
|
{ |
336
|
|
|
$this->log->info('Step 4 : restore foreign keys'); |
337
|
|
|
foreach ($foreignKeys as $tableName => $fks) { |
338
|
|
|
foreach ($fks as $fk) { |
339
|
|
|
$this->schemaManager->createForeignKey($fk, $tableName); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* @return mixed[] |
346
|
|
|
* @throws \Doctrine\DBAL\DBALException |
347
|
|
|
*/ |
348
|
|
|
private function dropMultipleUniqueContraints(): array |
349
|
|
|
{ |
350
|
|
|
$this->log->info('Step 2.2 : Drop Multiple indexes ...'); |
351
|
|
|
$multipleUniqueContraints = []; |
352
|
|
|
$tables = $this->schemaManager->listTables(); |
353
|
|
|
foreach ($tables as $table) { |
354
|
|
|
foreach ($table->getIndexes() as $index) { |
355
|
|
|
if ($index->isUnique() && count($index->getColumns()) > 1) { |
356
|
|
|
$multipleUniqueContraints[$table->getName()][] = $index; |
357
|
|
|
$this->schemaManager->dropIndex($index->getQuotedName($this->connection->getDatabasePlatform()), $table->getName()); |
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
return $multipleUniqueContraints; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* @param array<string, Index[]> $multipleUniqueContraints |
366
|
|
|
*/ |
367
|
|
|
private function restoreMultipleUniqueContraints(array $multipleUniqueContraints): void |
368
|
|
|
{ |
369
|
|
|
$this->log->info('Step 5 : restore multiple unique indexes keys'); |
370
|
|
|
foreach ($multipleUniqueContraints as $tableName => $indexes) { |
371
|
|
|
foreach ($indexes as $index) { |
372
|
|
|
$this->schemaManager->createIndex($index, $tableName); |
373
|
|
|
} |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* |
379
|
|
|
* Sets data for a group of columns (2 or more) that are bound by a unique constraint |
380
|
|
|
* @param array<string, Index[]> $multipleUniqueContraints |
381
|
|
|
* @param string[] $handledFKColumns |
382
|
|
|
* @return string[] |
383
|
|
|
* @throws \Doctrine\DBAL\DBALException |
384
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
385
|
|
|
*/ |
386
|
|
|
private function updateMultipleUniqueIndexedColumns(array $multipleUniqueContraints, array $handledFKColumns) : array |
387
|
|
|
{ |
388
|
|
|
|
389
|
|
|
foreach ($multipleUniqueContraints as $tableName => $indexes) { |
390
|
|
|
$table = $this->schemaManager->listTableDetails($tableName); |
391
|
|
|
|
392
|
|
|
foreach ($indexes as $index) { |
393
|
|
|
foreach ($index->getColumns() as $columnName) { |
394
|
|
|
$fullColumnName = $tableName. '.' .$columnName; |
395
|
|
|
if (!\in_array($fullColumnName, $handledFKColumns, true)) { |
396
|
|
|
$handledFKColumns[] = $fullColumnName; |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
$stmt = $this->connection->query('SELECT * FROM ' .$tableName); |
402
|
|
|
$count = $this->connection->fetchColumn('SELECT count(*) FROM ' .$tableName); |
403
|
|
|
$i = 1; |
404
|
|
|
while ($row = $stmt->fetch()) { |
405
|
|
|
$newValues = []; |
406
|
|
|
foreach ($indexes as $index) { |
407
|
|
|
/** @var Index $index */ |
408
|
|
|
$compoundColumnGenerator = $this->getCompoundColumnGenerator($table, $index, $count); |
409
|
|
|
$newValues = array_merge($newValues, $compoundColumnGenerator()); |
410
|
|
|
} |
411
|
|
|
$this->connection->update($tableName, $newValues, $this->stripUnselectableColumns($table, $row)); |
412
|
|
|
$this->log->info("Updated $i of $count for $tableName"); |
413
|
|
|
$i++; |
414
|
|
|
} |
415
|
|
|
} |
416
|
|
|
return $handledFKColumns; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* @param array<string, ForeignKeyConstraint[]> $foreignKeys |
421
|
|
|
* @param string[] $handledFKColumns |
422
|
|
|
* @throws \DBFaker\Exceptions\DBFakerException |
423
|
|
|
* @throws \Doctrine\DBAL\DBALException |
424
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
425
|
|
|
*/ |
426
|
|
|
private function updateRemainingForeignKeys(array $foreignKeys, array $handledFKColumns): void |
427
|
|
|
{ |
428
|
|
|
foreach ($foreignKeys as $tableName => $fks) { |
429
|
|
|
if (!array_key_exists($tableName, $this->fakeTableRowNumbers)) { |
430
|
|
|
//only update tables where data has been inserted |
431
|
|
|
continue; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
$table = $this->schemaManager->listTableDetails($tableName); |
435
|
|
|
|
436
|
|
|
$stmt = $this->connection->query('SELECT * FROM ' .$tableName); |
437
|
|
|
$count = $this->connection->fetchColumn("SELECT count(*) FROM ".$tableName); |
438
|
|
|
$i = 1; |
439
|
|
|
while ($row = $stmt->fetch()) { |
440
|
|
|
$newValues = []; |
441
|
|
|
foreach ($fks as $fk) { |
442
|
|
|
$localColumns = $fk->getLocalColumns(); |
443
|
|
|
foreach ($localColumns as $index => $localColumn) { |
444
|
|
|
if (\in_array($tableName . '.' . $localColumn, $handledFKColumns)) { |
445
|
|
|
continue; |
446
|
|
|
} |
447
|
|
|
$column = $table->getColumn($localColumn); |
448
|
|
|
$fkValueGenerator = $this->getForeignKeyColumnGenerator($table, $column); |
449
|
|
|
$newValues[$localColumn] = $fkValueGenerator(); |
450
|
|
|
} |
451
|
|
|
} |
452
|
|
|
$row = $this->stripUnselectableColumns($table, $row); |
453
|
|
|
if (count($newValues) && $this->connection->update($tableName, $newValues, $row) === 0) { |
454
|
|
|
throw new DBFakerException("Row has not been updated $tableName - ". var_export($newValues, true) . ' - ' . var_export($row, true)); |
455
|
|
|
}; |
456
|
|
|
$this->log->info("Step 3.3 : updated $i of $count for $tableName"); |
457
|
|
|
$i++; |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* @param ForeignKeyConstraint[] $extensionConstraints |
464
|
|
|
* @return string[] |
465
|
|
|
* @throws \DBFaker\Exceptions\DBFakerException |
466
|
|
|
* @throws \Doctrine\DBAL\DBALException |
467
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
468
|
|
|
* @throws \Exception |
469
|
|
|
*/ |
470
|
|
|
private function updateExtensionContraints(array $extensionConstraints) : array |
471
|
|
|
{ |
472
|
|
|
$handledFKColumns = []; |
473
|
|
|
foreach ($extensionConstraints as $fk) { |
474
|
|
|
$localTableName = $fk->getLocalTable()->getQuotedName($this->connection->getDatabasePlatform()); |
475
|
|
|
$stmt = $this->connection->query('SELECT * FROM ' . $localTableName); |
476
|
|
|
$count = $this->connection->fetchColumn('SELECT count(*) FROM ' .$localTableName); |
477
|
|
|
$i = 1; |
478
|
|
|
while ($row = $stmt->fetch()) { |
479
|
|
|
$newValues = []; |
480
|
|
|
$localColumns = $fk->getLocalColumns(); |
481
|
|
|
foreach ($localColumns as $index => $localColumn) { |
482
|
|
|
$handledFKColumns[] = $fk->getLocalTable()->getName() . '.' . $localColumn; |
483
|
|
|
$column = $fk->getLocalTable()->getColumn($localColumn); |
484
|
|
|
$fkValueGenerator = $this->getForeignKeyColumnGenerator($fk->getLocalTable(), $column); |
485
|
|
|
$newValues[$localColumn] = $fkValueGenerator(); |
486
|
|
|
} |
487
|
|
|
$row = $this->stripUnselectableColumns($fk->getLocalTable(), $row); |
488
|
|
|
if ($this->connection->update($localTableName, $newValues, $row) === 0) { |
489
|
|
|
throw new DBFakerException("Row has not been updated $localTableName - ". var_export($newValues, true) . ' - ' . var_export($row, true)); |
490
|
|
|
}; |
491
|
|
|
$this->log->info("Updated $i of $count for $localTableName"); |
492
|
|
|
$i++; |
493
|
|
|
} |
494
|
|
|
} |
495
|
|
|
return $handledFKColumns; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* @param Table $table |
500
|
|
|
* @param Index $index |
501
|
|
|
* @return CompoundColumnGenerator |
502
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
503
|
|
|
* @throws \DBFaker\Exceptions\SchemaLogicException |
504
|
|
|
* @throws \DBFaker\Exceptions\UnsupportedDataTypeException |
505
|
|
|
*/ |
506
|
|
|
private function getCompoundColumnGenerator(Table $table, Index $index, int $count): CompoundColumnGenerator |
507
|
|
|
{ |
508
|
|
|
if (!isset($this->compoundColumnGenerators[$table->getName() . "." . $index->getName()])) { |
509
|
|
|
$compoundGenerator = new CompoundColumnGenerator($table, $index, $this->schemaHelper, $this, $this->schemaManager, $this->fakerManagerHelper, $count); |
510
|
|
|
$this->compoundColumnGenerators[$table->getName() . '.' . $index->getName()] = $compoundGenerator; |
511
|
|
|
} |
512
|
|
|
return $this->compoundColumnGenerators[$table->getName() . '.' . $index->getName()]; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* @param Table $table |
517
|
|
|
* @param Column $column |
518
|
|
|
* @return ForeignKeyColumnGenerator |
519
|
|
|
* @throws \DBFaker\Exceptions\SchemaLogicException |
520
|
|
|
*/ |
521
|
|
|
public function getForeignKeyColumnGenerator(Table $table, Column $column): ForeignKeyColumnGenerator |
522
|
|
|
{ |
523
|
|
|
$identifier = $table->getName() . '.' . $column->getName(); |
524
|
|
|
return $this->fkColumnsGenerators[$identifier]; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* @param Table $table |
529
|
|
|
* @param Column $column |
530
|
|
|
* @return FakeDataGeneratorInterface |
531
|
|
|
* @throws \DBFaker\Exceptions\UnsupportedDataTypeException |
532
|
|
|
*/ |
533
|
|
|
public function getSimpleColumnGenerator(Table $table, Column $column) : FakeDataGeneratorInterface |
534
|
|
|
{ |
535
|
|
|
return $this->generatorFinder->findGenerator($table, $column, $this->schemaHelper); |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
/** |
539
|
|
|
* @param Table $table |
540
|
|
|
* @param mixed[] $row |
541
|
|
|
* @return mixed[] |
542
|
|
|
* @throws \Doctrine\DBAL\Schema\SchemaException |
543
|
|
|
*/ |
544
|
|
|
private function stripUnselectableColumns(Table $table, array $row) : array |
545
|
|
|
{ |
546
|
|
|
return array_filter($row, function (string $columnName) use ($table) { |
547
|
|
|
return !\in_array($table->getColumn($columnName)->getType()->getName(), [ |
548
|
|
|
Type::BINARY, Type::JSON, Type::JSON_ARRAY, Type::SIMPLE_ARRAY, Type::TARRAY, Type::BLOB, Type::JSON, Type::OBJECT |
549
|
|
|
], true); |
550
|
|
|
}, ARRAY_FILTER_USE_KEY); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* @throws \Doctrine\DBAL\DBALException |
555
|
|
|
* @return ForeignKeyConstraint[] |
556
|
|
|
*/ |
557
|
|
|
private function getExtensionConstraints() : array |
558
|
|
|
{ |
559
|
|
|
$this->log->info('Step 2.1 : store extension constraints ...'); |
560
|
|
|
$extensionConstraints = []; |
561
|
|
|
$tables = $this->schemaManager->listTables(); |
562
|
|
|
foreach ($tables as $table) { |
563
|
|
|
foreach ($table->getForeignKeys() as $fk) { |
564
|
|
|
if ($this->schemaHelper->isExtendingKey($fk)) { |
565
|
|
|
$extensionConstraints[] = $fk; |
566
|
|
|
} |
567
|
|
|
} |
568
|
|
|
} |
569
|
|
|
return $extensionConstraints; |
570
|
|
|
} |
571
|
|
|
} |
572
|
|
|
|
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths