Completed
Push — master ( adc131...6e9eb4 )
by Kevin
03:32
created

DBFaker::getSimpleColumnGenerator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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
Bug introduced by
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
introduced by
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
                }
166
                //Other data will be Faked depending of column's type and attributes. FKs to, but their values wil be overridden.
167
                else {
168
                    if (!$column->getNotnull() && $this->nullProbabilityOccured()){
169
                        $value = null;
170
                    }else{
171
                        $generator = $this->getSimpleColumnGenerator($table, $column);
172
                        $value = $generator();
173
                    }
174
                }
175
                $row[$column->getName()] = $value;
176
            }
177
            $data[] = $row;
178
        }
179
        return $data;
180
    }
181
182
    /**
183
     * Inserts the data. This is done in 2 steps :
184
     *   - 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.
185
     *   - second turn will update FKs to set random PK values from the previously generated lines.
186
     * @param $data
187
     * @throws \Doctrine\DBAL\DBALException
188
     * @throws \Doctrine\DBAL\Schema\SchemaException
189
     */
190
    private function insertFakeData($data, $extensionContraints, $multipleUniqueContraints, $foreignKeys) : void
191
    {
192
        //1 - First insert data with no FKs, and null PKs. This will generate primary keys
193
        $this->log->info('Step 3.1 : Insert simple data ...');
194
        $this->insertWithoutFksAndUniqueIndexes($data);
195
196
        //2 - loop on multiple unique index constraints (that may include FKs)
197
        $this->log->info('Step 3.2 : Update Multiple Unique Indexed Columns');
198
        $handledColumns = $this->updateExtensionContraints($extensionContraints);
199
200
        //2 - loop on multiple unique index constraints (that may include FKs)
201
        $this->log->info('Step 3.3 : Update Multiple Unique Indexed Columns');
202
        $handledColumns = $this->updateMultipleUniqueIndexedColumns($multipleUniqueContraints, $handledColumns);
203
204
        //3 - loop again to set FKs now that all PK have been loaded
205
        $this->log->info('Step 3.4 : Update Remaining ForeignKeys');
206
        $this->updateRemainingForeignKeys($foreignKeys, $handledColumns);
207
    }
208
209
    /**
210
     * Inserts base data :
211
     *    - AutoIncrement PKs will be generated and stored
212
     *    - ForeignKey and Multiple Unique Indexes are ignored, because we need self-generated PK values
213
     * @param mixed[] $data
214
     * @throws \Doctrine\DBAL\DBALException
215
     * @throws \Doctrine\DBAL\Schema\SchemaException
216
     */
217
    private function insertWithoutFksAndUniqueIndexes($data): void
218
    {
219
        $plateform = $this->connection->getDatabasePlatform();
220
        foreach ($data as $tableName => $rows){
221
            $table = $this->schemaManager->listTableDetails($tableName);
222
223
            //initiate column types for insert : only get the first array to retrieve column names
224
            $types = [];
225
            $first = reset($rows);
226
            if ($first){
227
                foreach ($first as $columnName => $value){
228
                    /** @var Column $column */
229
                    $column = $table->getColumn($columnName);
230
                    $types[] = $column->getType()->getBindingType();
231
                }
232
            }
233
234
            //insert faked data
235
            $cnt = count($rows);
236
            foreach ($rows as $index => $row){
237
                $dbRow = [];
238
                foreach ($row as $columnName => $value){
239
                    $column = $table->getColumn($columnName);
240
                    $newVal = $column->getType()->convertToDatabaseValue($value, $plateform);
241
                    $dbRow[$column->getQuotedName($this->connection->getDatabasePlatform())] = $newVal;
242
                }
243
                $this->log->info("Step 3.1 : Inserted $index of $cnt in $tableName");
244
                $this->connection->insert($table->getName(), $dbRow, $types);
245
            }
246
            //if autoincrement, add the new ID to the PKRegistry
247
            if ($table->hasPrimaryKey()){
248
                $pkColumnName = $table->getPrimaryKeyColumns()[0];
249
                $pkColumn = $table->getColumn($pkColumnName);
250
                if ($pkColumn->getAutoincrement() && $this->schemaHelper->isPrimaryKeyColumn($table, $pkColumn)){
251
                    $this->getPkRegistry($table)->addValue([$pkColumnName => $this->connection->lastInsertId()]);
252
                }
253
            }
254
        }
255
    }
256
257
    /**
258
     * @param Table $table
259
     * @return PrimaryKeyRegistry
260
     * @throws \Doctrine\DBAL\DBALException
261
     * @throws \DBFaker\Exceptions\SchemaLogicException
262
     */
263
    public function getPkRegistry(Table $table, $isSelfReferencing = false) : PrimaryKeyRegistry
264
    {
265
        $index = $table->getName().($isSelfReferencing ? 'dbfacker_self_referencing' :'');
266
        if (!isset($this->primaryKeyRegistries[$index])) {
267
            $this->primaryKeyRegistries[$index] = new PrimaryKeyRegistry($this->connection, $table, $this->schemaHelper, $isSelfReferencing);
268
        }
269
        return $this->primaryKeyRegistries[$index];
270
    }
271
272
    /**
273
     * @return bool : if null value should be generated
274
     * @throws \Exception
275
     */
276
    private function nullProbabilityOccured() : bool
277
    {
278
        return random_int(0, 100) < $this->nullProbability;
279
    }
280
281
    /**
282
     * Sets the number of lines that should be generated for each table
283
     * @param int[] $fakeTableRowNumbers : associative array - Key is the name of the table, and value the number of lines to the faked
284
     */
285
    public function setFakeTableRowNumbers(array $fakeTableRowNumbers) : void
286
    {
287
        $this->fakeTableRowNumbers = $fakeTableRowNumbers;
288
    }
289
290
    /**
291
     * Sets the null probability : chance to generate a null value for nullable columns (between 0 and 100, default is 10)
292
     * @param int $nullProbability
293
     */
294
    public function setNullProbability(int $nullProbability) : void
295
    {
296
        $this->nullProbability = $nullProbability;
297
    }
298
299
    /**
300
     * Drop all foreign keys because it is too complicated to solve the table reference graph in order to generate data in the right order.
301
     * FKs are stored to be recreated at the end
302
     * @throws \Doctrine\DBAL\DBALException
303
     * @throws \DBFaker\Exceptions\SchemaLogicException
304
     * @throws \Doctrine\DBAL\Schema\SchemaException
305
     * @return mixed[]
306
     */
307
    private function dropForeignKeys() : array
308
    {
309
        $this->log->info('Step 2.1 : Drop FKs ...');
310
        $foreignKeys = [];
311
        $tables = $this->schemaManager->listTables();
312
        foreach ($tables as $table){
313
            foreach ($table->getForeignKeys() as $fk){
314
                $foreignTable = $this->schemaManager->listTableDetails($fk->getForeignTableName());
315
                foreach ($fk->getColumns() as $localColumnName){
316
                    $localColumn = $table->getColumn($localColumnName);
317
                    $selfReferencing = $fk->getForeignTableName() === $table->getName();
318
                    $fkValueGenerator = new ForeignKeyColumnGenerator($table, $localColumn, $this->getPkRegistry($foreignTable, $selfReferencing), $fk, $this->fakerManagerHelper, $this->schemaHelper);
319
                    $this->fkColumnsGenerators[$table->getName() . '.' . $localColumnName] = $fkValueGenerator;
320
                }
321
                $foreignKeys[$table->getName()][] = $fk;
322
                $this->schemaManager->dropForeignKey($fk, $table);
323
            }
324
        }
325
        return $foreignKeys;
326
    }
327
328
    /**
329
     * Restore the foreign keys based on the ForeignKeys store built when calling dropForeignKeys()
330
     * @param mixed $foreignKeys
331
     */
332
    private function restoreForeignKeys($foreignKeys) : void
333
    {
334
        $this->log->info('Step 4 : restore foreign keys');
335
        foreach ($foreignKeys as $tableName => $fks){
336
            foreach ($fks as $fk){
337
                $this->schemaManager->createForeignKey($fk, $tableName);
338
            }
339
        }
340
    }
341
342
    /**
343
     * @return mixed[]
344
     * @throws \Doctrine\DBAL\DBALException
345
     */
346
    private function dropMultipleUniqueContraints(): array
347
    {
348
        $this->log->info('Step 2.2 : Drop Multiple indexes ...');
349
        $multipleUniqueContraints = [];
350
        $tables = $this->schemaManager->listTables();
351
        foreach ($tables as $table){
352
            foreach ($table->getIndexes() as $index){
353
                if ($index->isUnique() && count($index->getColumns()) > 1){
354
                    $multipleUniqueContraints[$table->getName()][] = $index;
355
                    $this->schemaManager->dropIndex($index->getQuotedName($this->connection->getDatabasePlatform()), $table->getName());
356
                }
357
            }
358
        }
359
        return $multipleUniqueContraints;
360
    }
361
362
    /**
363
     * @param mixed[] $multipleUniqueContraints
364
     */
365
    private function restoreMultipleUniqueContraints($multipleUniqueContraints): void
366
    {
367
        $this->log->info('Step 5 : restore multiple unique indexes keys');
368
        foreach ($multipleUniqueContraints as $tableName => $indexes){
369
            foreach ($indexes as $index){
370
                $this->schemaManager->createIndex($index, $tableName);
371
            }
372
        }
373
    }
374
375
    /**
376
     *
377
     * Sets data for a group of columns (2 or more) that are bound by a unique constraint
378
     * @param mixed[] $multipleUniqueContraints
379
     * @param string[] $handledFKColumns
380
     * @return array
381
     * @throws \Doctrine\DBAL\DBALException
382
     * @throws \Doctrine\DBAL\Schema\SchemaException
383
     */
384
    private function updateMultipleUniqueIndexedColumns($multipleUniqueContraints, $handledFKColumns) : array
385
    {
386
387
        foreach ($multipleUniqueContraints as $tableName => $indexes){
388
            $table = $this->schemaManager->listTableDetails($tableName);
389
390
            foreach ($indexes as $index){
391
                foreach ($index->getColumns() as $columnName){
392
                    $fullColumnName = $tableName. '.' .$columnName;
393
                    if (!\in_array($fullColumnName, $handledFKColumns, true)){
394
                        $handledFKColumns[] = $fullColumnName;
395
                    }
396
                }
397
            }
398
399
            $stmt = $this->connection->query('SELECT * FROM ' .$tableName);
400
            $count = $this->connection->fetchColumn('SELECT count(*) FROM ' .$tableName);
401
            $i = 1;
402
            while ($row = $stmt->fetch()) {
403
                $newValues = [];
404
                foreach ($indexes as $index){
405
                    /** @var Index $index */
406
                    $compoundColumnGenerator = $this->getCompoundColumnGenerator($table, $index, $count);
407
                    $newValues = array_merge($newValues, $compoundColumnGenerator());
408
                }
409
                $this->connection->update($tableName, $newValues, $this->stripUnselectableColumns($table, $row));
410
                $this->log->info("Updated $i of $count for $tableName");
411
                $i++;
412
            }
413
        }
414
        return $handledFKColumns;
415
    }
416
417
    /**
418
     * @param mixed[] $foreignKeys
419
     * @param string[] $handledFKColumns
420
     * @throws \DBFaker\Exceptions\DBFakerException
421
     * @throws \Doctrine\DBAL\DBALException
422
     * @throws \Doctrine\DBAL\Schema\SchemaException
423
     */
424
    private function updateRemainingForeignKeys($foreignKeys, $handledFKColumns): void
425
    {
426
        foreach ($foreignKeys as $tableName => $fks){
427
            if (!array_key_exists($tableName, $this->fakeTableRowNumbers)){
428
                //only update tables where data has been inserted
429
                continue;
430
            }
431
432
            $table = $this->schemaManager->listTableDetails($tableName);
433
434
            $stmt = $this->connection->query('SELECT * FROM ' .$tableName);
435
            $count = $this->connection->fetchColumn("SELECT count(*) FROM ".$tableName);
436
            $i = 1;
437
            while ($row = $stmt->fetch()) {
438
                $newValues = [];
439
                foreach ($fks as $fk) {
440
                    $localColumns = $fk->getLocalColumns();
441
                    foreach ($localColumns as $index => $localColumn) {
442
                        if (\in_array($tableName . '.' . $localColumn, $handledFKColumns)){
443
                            continue;
444
                        }
445
                        $column = $table->getColumn($localColumn);
446
                        $fkValueGenerator = $this->getForeignKeyColumnGenerator($table, $column);
447
                        $newValues[$localColumn] = $fkValueGenerator();
448
                    }
449
                }
450
                $row = $this->stripUnselectableColumns($table, $row);
451
                if (count($newValues) && $this->connection->update($tableName, $newValues, $row) === 0){
452
                    throw new DBFakerException("Row has not been updated $tableName - ". var_export($newValues,true) . ' - ' . var_export($row, true));
453
                };
454
                $this->log->info("Step 3.3 : updated $i of $count for $tableName");
455
                $i++;
456
            }
457
        }
458
    }
459
460
    /**
461
     * @param mixed[] $extensionConstraints
462
     * @return string[]
463
     * @throws \DBFaker\Exceptions\DBFakerException
464
     * @throws \Doctrine\DBAL\DBALException
465
     * @throws \Doctrine\DBAL\Schema\SchemaException
466
     * @throws \Exception
467
     */
468
    private function updateExtensionContraints($extensionConstraints) : array
469
    {
470
        $handledFKColumns = [];
471
        foreach ($extensionConstraints as $fk){
472
            $localTableName = $fk->getLocalTable()->getQuotedName($this->connection->getDatabasePlatform());
473
            $stmt = $this->connection->query('SELECT * FROM ' . $localTableName);
474
            $count = $this->connection->fetchColumn('SELECT count(*) FROM ' .$localTableName);
475
            $i = 1;
476
            while ($row = $stmt->fetch()) {
477
                $newValues = [];
478
                $localColumns = $fk->getLocalColumns();
479
                foreach ($localColumns as $index => $localColumn) {
480
                    $handledFKColumns[] = $fk->getLocalTable()->getName() . '.' . $localColumn;
481
                    $column = $fk->getLocalTable()->getColumn($localColumn);
482
                    $fkValueGenerator = $this->getForeignKeyColumnGenerator($fk->getLocalTable(), $column);
483
                    $newValues[$localColumn] = $fkValueGenerator();
484
                }
485
                $row = $this->stripUnselectableColumns($fk->getLocalTable(), $row);
486
                if ($this->connection->update($localTableName, $newValues, $row) === 0){
487
                    throw new DBFakerException("Row has not been updated $localTableName - ". var_export($newValues,true) . ' - ' . var_export($row, true));
488
                };
489
                $this->log->info("Updated $i of $count for $localTableName");
490
                $i++;
491
            }
492
        }
493
        return $handledFKColumns;
494
    }
495
496
    /**
497
     * @param Table $table
498
     * @param Index $index
499
     * @return CompoundColumnGenerator
500
     * @throws \Doctrine\DBAL\Schema\SchemaException
501
     * @throws \DBFaker\Exceptions\SchemaLogicException
502
     * @throws \DBFaker\Exceptions\UnsupportedDataTypeException
503
     */
504
    private function getCompoundColumnGenerator(Table $table, Index $index, int $count): CompoundColumnGenerator
505
    {
506
        if (!isset($this->compoundColumnGenerators[$table->getName() . "." . $index->getName()])){
507
            $compoundGenerator = new CompoundColumnGenerator($table, $index, $this->schemaHelper, $this, $this->schemaManager, $this->fakerManagerHelper, $count);
508
            $this->compoundColumnGenerators[$table->getName() . '.' . $index->getName()] = $compoundGenerator;
509
        }
510
        return $this->compoundColumnGenerators[$table->getName() . '.' . $index->getName()];
511
    }
512
513
    /**
514
     * @param Table $table
515
     * @param Column $column
516
     * @return ForeignKeyColumnGenerator
517
     * @throws \DBFaker\Exceptions\SchemaLogicException
518
     */
519
    public function getForeignKeyColumnGenerator(Table $table, Column $column): ForeignKeyColumnGenerator
520
    {
521
        $identifier = $table->getName() . '.' . $column->getName();
522
        return $this->fkColumnsGenerators[$identifier];
523
    }
524
525
    /**
526
     * @param Table $table
527
     * @param Column $column
528
     * @return FakeDataGeneratorInterface
529
     * @throws \DBFaker\Exceptions\UnsupportedDataTypeException
530
     */
531
    public function getSimpleColumnGenerator(Table $table, Column $column) : FakeDataGeneratorInterface
532
    {
533
        return $this->generatorFinder->findGenerator($table, $column, $this->schemaHelper);
534
    }
535
536
    /**
537
     * @param Table $table
538
     * @param array $row
539
     * @return mixed[]
540
     * @throws \Doctrine\DBAL\Schema\SchemaException
541
     */
542
    private function stripUnselectableColumns(Table $table, array $row) : array
543
    {
544
        return array_filter($row, function(string $columnName) use ($table) {
545
            return !\in_array($table->getColumn($columnName)->getType()->getName(), [
546
                Type::BINARY, Type::JSON, Type::JSON_ARRAY, Type::SIMPLE_ARRAY, Type::TARRAY, Type::BLOB, Type::JSON, Type::OBJECT
547
            ],true);
548
        }, ARRAY_FILTER_USE_KEY);
549
    }
550
551
    /**
552
     * @throws \Doctrine\DBAL\DBALException
553
     * @return ForeignKeyConstraint[]
554
     */
555
    private function getExtensionConstraints() : array
556
    {
557
        $this->log->info('Step 2.1 : store extension constraints ...');
558
        $extensionConstraints = [];
559
        $tables = $this->schemaManager->listTables();
560
        foreach ($tables as $table){
561
            foreach ($table->getForeignKeys() as $fk){
562
                if ($this->schemaHelper->isExtendingKey($fk)){
563
                    $extensionConstraints[] = $fk;
564
                }
565
            }
566
        }
567
        return $extensionConstraints;
568
    }
569
570
}
571