Issues (13)

src/DBFaker.php (2 issues)

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;
0 ignored issues
show
The type DBFaker\Generators\GeneratorFactory 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...
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 = [];
0 ignored issues
show
The private property $handledFKColumns is not used, and could be removed.
Loading history...
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