Total Complexity | 145 |
Total Lines | 855 |
Duplicated Lines | 2.11 % |
Coverage | 87.07% |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like SchemaTool often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SchemaTool, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class SchemaTool |
||
47 | { |
||
48 | /** |
||
49 | * @var \Doctrine\ORM\EntityManagerInterface |
||
50 | */ |
||
51 | private $em; |
||
52 | |||
53 | /** |
||
54 | * @var \Doctrine\DBAL\Platforms\AbstractPlatform |
||
55 | */ |
||
56 | private $platform; |
||
57 | |||
58 | /** |
||
59 | * The quote strategy. |
||
60 | * |
||
61 | * @var \Doctrine\ORM\Mapping\QuoteStrategy |
||
62 | */ |
||
63 | private $quoteStrategy; |
||
64 | |||
65 | /** |
||
66 | * Initializes a new SchemaTool instance that uses the connection of the |
||
67 | * provided EntityManager. |
||
68 | * |
||
69 | * @param \Doctrine\ORM\EntityManagerInterface $em |
||
70 | */ |
||
71 | 1298 | public function __construct(EntityManagerInterface $em) |
|
76 | 1298 | } |
|
77 | |||
78 | /** |
||
79 | * Creates the database schema for the given array of ClassMetadata instances. |
||
80 | * |
||
81 | * @param array $classes |
||
82 | * |
||
83 | * @return void |
||
84 | * |
||
85 | * @throws ToolsException |
||
86 | */ |
||
87 | 288 | public function createSchema(array $classes) |
|
88 | { |
||
89 | 288 | $createSchemaSql = $this->getCreateSchemaSql($classes); |
|
90 | 288 | $conn = $this->em->getConnection(); |
|
91 | |||
92 | 288 | foreach ($createSchemaSql as $sql) { |
|
93 | try { |
||
94 | 288 | $conn->executeQuery($sql); |
|
95 | 90 | } catch (\Throwable $e) { |
|
96 | 288 | throw ToolsException::schemaToolFailure($sql, $e); |
|
97 | } |
||
98 | } |
||
99 | 198 | } |
|
100 | |||
101 | /** |
||
102 | * Gets the list of DDL statements that are required to create the database schema for |
||
103 | * the given list of ClassMetadata instances. |
||
104 | * |
||
105 | * @param array $classes |
||
106 | * |
||
107 | * @return array The SQL statements needed to create the schema for the classes. |
||
108 | */ |
||
109 | 288 | public function getCreateSchemaSql(array $classes) |
|
114 | } |
||
115 | |||
116 | /** |
||
117 | * Detects instances of ClassMetadata that don't need to be processed in the SchemaTool context. |
||
118 | * |
||
119 | * @param ClassMetadata $class |
||
120 | * @param array $processedClasses |
||
121 | * |
||
122 | * @return bool |
||
123 | */ |
||
124 | 298 | private function processingNotRequired($class, array $processedClasses) |
|
131 | ); |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * Creates a Schema instance from a given set of metadata classes. |
||
136 | * |
||
137 | * @param array $classes |
||
138 | * |
||
139 | * @return Schema |
||
140 | * |
||
141 | * @throws \Doctrine\ORM\ORMException |
||
142 | */ |
||
143 | 298 | public function getSchemaFromMetadata(array $classes) |
|
144 | { |
||
145 | // Reminder for processed classes, used for hierarchies |
||
146 | 298 | $processedClasses = []; |
|
147 | 298 | $eventManager = $this->em->getEventManager(); |
|
148 | 298 | $schemaManager = $this->em->getConnection()->getSchemaManager(); |
|
149 | 298 | $metadataSchemaConfig = $schemaManager->createSchemaConfig(); |
|
150 | |||
151 | 298 | $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); |
|
152 | 298 | $schema = new Schema([], [], $metadataSchemaConfig); |
|
153 | |||
154 | 298 | $addedFks = []; |
|
155 | 298 | $blacklistedFks = []; |
|
156 | |||
157 | 298 | foreach ($classes as $class) { |
|
158 | /** @var \Doctrine\ORM\Mapping\ClassMetadata $class */ |
||
159 | 298 | if ($this->processingNotRequired($class, $processedClasses)) { |
|
160 | 39 | continue; |
|
161 | } |
||
162 | |||
163 | 298 | $table = $schema->createTable($this->quoteStrategy->getTableName($class, $this->platform)); |
|
164 | |||
165 | 298 | if ($class->isInheritanceTypeSingleTable()) { |
|
166 | 36 | $this->gatherColumns($class, $table); |
|
167 | 36 | $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); |
|
168 | |||
169 | // Add the discriminator column |
||
170 | 36 | $this->addDiscriminatorColumnDefinition($class, $table); |
|
171 | |||
172 | // Aggregate all the information from all classes in the hierarchy |
||
173 | 36 | foreach ($class->parentClasses as $parentClassName) { |
|
174 | // Parent class information is already contained in this class |
||
175 | $processedClasses[$parentClassName] = true; |
||
176 | } |
||
177 | |||
178 | 36 | foreach ($class->subClasses as $subClassName) { |
|
179 | 34 | $subClass = $this->em->getClassMetadata($subClassName); |
|
180 | 34 | $this->gatherColumns($subClass, $table); |
|
181 | 34 | $this->gatherRelationsSql($subClass, $table, $schema, $addedFks, $blacklistedFks); |
|
182 | 36 | $processedClasses[$subClassName] = true; |
|
183 | } |
||
184 | 294 | } elseif ($class->isInheritanceTypeJoined()) { |
|
185 | // Add all non-inherited fields as columns |
||
186 | 60 | foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
187 | 60 | if ( ! isset($mapping['inherited'])) { |
|
188 | 60 | $this->gatherColumn($class, $mapping, $table); |
|
189 | } |
||
190 | } |
||
191 | |||
192 | 60 | $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); |
|
193 | |||
194 | // Add the discriminator column only to the root table |
||
195 | 60 | if ($class->name == $class->rootEntityName) { |
|
196 | 60 | $this->addDiscriminatorColumnDefinition($class, $table); |
|
197 | } else { |
||
198 | // Add an ID FK column to child tables |
||
199 | 59 | $pkColumns = []; |
|
200 | 59 | $inheritedKeyColumns = []; |
|
201 | |||
202 | 59 | foreach ($class->identifier as $identifierField) { |
|
203 | 59 | if (isset($class->fieldMappings[$identifierField]['inherited'])) { |
|
204 | 59 | $idMapping = $class->fieldMappings[$identifierField]; |
|
205 | 59 | $this->gatherColumn($class, $idMapping, $table); |
|
206 | 59 | $columnName = $this->quoteStrategy->getColumnName( |
|
207 | 59 | $identifierField, |
|
208 | 59 | $class, |
|
209 | 59 | $this->platform |
|
210 | ); |
||
211 | // TODO: This seems rather hackish, can we optimize it? |
||
212 | 59 | $table->getColumn($columnName)->setAutoincrement(false); |
|
213 | |||
214 | 59 | $pkColumns[] = $columnName; |
|
215 | 59 | $inheritedKeyColumns[] = $columnName; |
|
216 | |||
217 | 59 | continue; |
|
218 | } |
||
219 | |||
220 | 2 | if (isset($class->associationMappings[$identifierField]['inherited'])) { |
|
221 | 1 | $idMapping = $class->associationMappings[$identifierField]; |
|
222 | |||
223 | 1 | $targetEntity = current( |
|
224 | 1 | array_filter( |
|
225 | 1 | $classes, |
|
226 | 1 | function (ClassMetadata $class) use ($idMapping) : bool { |
|
227 | 1 | return $class->name === $idMapping['targetEntity']; |
|
228 | 1 | } |
|
229 | ) |
||
230 | ); |
||
231 | |||
232 | 1 | foreach ($idMapping['joinColumns'] as $joinColumn) { |
|
233 | 1 | View Code Duplication | if (isset($targetEntity->fieldMappings[$joinColumn['referencedColumnName']])) { |
|
|||
234 | 1 | $columnName = $this->quoteStrategy->getJoinColumnName( |
|
235 | 1 | $joinColumn, |
|
236 | 1 | $class, |
|
237 | 1 | $this->platform |
|
238 | ); |
||
239 | |||
240 | 1 | $pkColumns[] = $columnName; |
|
241 | 2 | $inheritedKeyColumns[] = $columnName; |
|
242 | } |
||
243 | } |
||
244 | } |
||
245 | } |
||
246 | |||
247 | 59 | if ( ! empty($inheritedKeyColumns)) { |
|
248 | // Add a FK constraint on the ID column |
||
249 | 59 | $table->addForeignKeyConstraint( |
|
250 | 59 | $this->quoteStrategy->getTableName( |
|
251 | 59 | $this->em->getClassMetadata($class->rootEntityName), |
|
252 | 59 | $this->platform |
|
253 | ), |
||
254 | 59 | $inheritedKeyColumns, |
|
255 | 59 | $inheritedKeyColumns, |
|
256 | 59 | ['onDelete' => 'CASCADE'] |
|
257 | ); |
||
258 | } |
||
259 | |||
260 | 59 | if ( ! empty($pkColumns)) { |
|
261 | 60 | $table->setPrimaryKey($pkColumns); |
|
262 | } |
||
263 | } |
||
264 | 272 | } elseif ($class->isInheritanceTypeTablePerClass()) { |
|
265 | throw ORMException::notSupported(); |
||
266 | } else { |
||
267 | 272 | $this->gatherColumns($class, $table); |
|
268 | 272 | $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); |
|
269 | } |
||
270 | |||
271 | 298 | $pkColumns = []; |
|
272 | |||
273 | 298 | foreach ($class->identifier as $identifierField) { |
|
274 | 298 | if (isset($class->fieldMappings[$identifierField])) { |
|
275 | 298 | $pkColumns[] = $this->quoteStrategy->getColumnName($identifierField, $class, $this->platform); |
|
276 | 31 | View Code Duplication | } elseif (isset($class->associationMappings[$identifierField])) { |
277 | /* @var $assoc \Doctrine\ORM\Mapping\OneToOne */ |
||
278 | 31 | $assoc = $class->associationMappings[$identifierField]; |
|
279 | |||
280 | 31 | foreach ($assoc['joinColumns'] as $joinColumn) { |
|
281 | 298 | $pkColumns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
282 | } |
||
283 | } |
||
284 | } |
||
285 | |||
286 | 298 | if ( ! $table->hasIndex('primary')) { |
|
287 | 298 | $table->setPrimaryKey($pkColumns); |
|
288 | } |
||
289 | |||
290 | // there can be unique indexes automatically created for join column |
||
291 | // if join column is also primary key we should keep only primary key on this column |
||
292 | // so, remove indexes overruled by primary key |
||
293 | 298 | $primaryKey = $table->getIndex('primary'); |
|
294 | |||
295 | 298 | foreach ($table->getIndexes() as $idxKey => $existingIndex) { |
|
296 | 298 | if ($primaryKey->overrules($existingIndex)) { |
|
297 | 298 | $table->dropIndex($idxKey); |
|
298 | } |
||
299 | } |
||
300 | |||
301 | 298 | if (isset($class->table['indexes'])) { |
|
302 | 1 | foreach ($class->table['indexes'] as $indexName => $indexData) { |
|
303 | 1 | if ( ! isset($indexData['flags'])) { |
|
304 | 1 | $indexData['flags'] = []; |
|
305 | } |
||
306 | |||
307 | 1 | $table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, (array) $indexData['flags'], $indexData['options'] ?? []); |
|
308 | } |
||
309 | } |
||
310 | |||
311 | 298 | if (isset($class->table['uniqueConstraints'])) { |
|
312 | 4 | foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) { |
|
313 | 4 | $uniqIndex = new Index($indexName, $indexData['columns'], true, false, [], $indexData['options'] ?? []); |
|
314 | |||
315 | 4 | foreach ($table->getIndexes() as $tableIndexName => $tableIndex) { |
|
316 | 4 | if ($tableIndex->isFullfilledBy($uniqIndex)) { |
|
317 | 3 | $table->dropIndex($tableIndexName); |
|
318 | 4 | break; |
|
319 | } |
||
320 | } |
||
321 | |||
322 | 4 | $table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []); |
|
323 | } |
||
324 | } |
||
325 | |||
326 | 298 | if (isset($class->table['options'])) { |
|
327 | 1 | foreach ($class->table['options'] as $key => $val) { |
|
328 | 1 | $table->addOption($key, $val); |
|
329 | } |
||
330 | } |
||
331 | |||
332 | 298 | $processedClasses[$class->name] = true; |
|
333 | |||
334 | 298 | if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) { |
|
335 | $seqDef = $class->sequenceGeneratorDefinition; |
||
336 | $quotedName = $this->quoteStrategy->getSequenceName($seqDef, $class, $this->platform); |
||
337 | if ( ! $schema->hasSequence($quotedName)) { |
||
338 | $schema->createSequence( |
||
339 | $quotedName, |
||
340 | $seqDef['allocationSize'], |
||
341 | $seqDef['initialValue'] |
||
342 | ); |
||
343 | } |
||
344 | } |
||
345 | |||
346 | 298 | if ($eventManager->hasListeners(ToolEvents::postGenerateSchemaTable)) { |
|
347 | 1 | $eventManager->dispatchEvent( |
|
348 | 1 | ToolEvents::postGenerateSchemaTable, |
|
349 | 298 | new GenerateSchemaTableEventArgs($class, $schema, $table) |
|
350 | ); |
||
351 | } |
||
352 | } |
||
353 | |||
354 | 298 | if ( ! $this->platform->supportsSchemas() && ! $this->platform->canEmulateSchemas()) { |
|
355 | 9 | $schema->visit(new RemoveNamespacedAssets()); |
|
356 | } |
||
357 | |||
358 | 298 | if ($eventManager->hasListeners(ToolEvents::postGenerateSchema)) { |
|
359 | 1 | $eventManager->dispatchEvent( |
|
360 | 1 | ToolEvents::postGenerateSchema, |
|
361 | 1 | new GenerateSchemaEventArgs($this->em, $schema) |
|
362 | ); |
||
363 | } |
||
364 | |||
365 | 298 | return $schema; |
|
366 | } |
||
367 | |||
368 | /** |
||
369 | * Gets a portable column definition as required by the DBAL for the discriminator |
||
370 | * column of a class. |
||
371 | * |
||
372 | * @param ClassMetadata $class |
||
373 | * @param Table $table |
||
374 | * |
||
375 | * @return void |
||
376 | */ |
||
377 | 91 | private function addDiscriminatorColumnDefinition($class, Table $table) |
|
378 | { |
||
379 | 91 | $discrColumn = $class->discriminatorColumn; |
|
380 | |||
381 | 91 | if ( ! isset($discrColumn['type']) || |
|
382 | 91 | (strtolower($discrColumn['type']) == 'string' && ! isset($discrColumn['length'])) |
|
383 | ) { |
||
384 | 1 | $discrColumn['type'] = 'string'; |
|
385 | 1 | $discrColumn['length'] = 255; |
|
386 | } |
||
387 | |||
388 | $options = [ |
||
389 | 91 | 'length' => $discrColumn['length'] ?? null, |
|
390 | 'notnull' => true |
||
391 | ]; |
||
392 | |||
393 | 91 | if (isset($discrColumn['columnDefinition'])) { |
|
394 | $options['columnDefinition'] = $discrColumn['columnDefinition']; |
||
395 | } |
||
396 | |||
397 | 91 | $table->addColumn($discrColumn['name'], $discrColumn['type'], $options); |
|
398 | 91 | } |
|
399 | |||
400 | /** |
||
401 | * Gathers the column definitions as required by the DBAL of all field mappings |
||
402 | * found in the given class. |
||
403 | * |
||
404 | * @param ClassMetadata $class |
||
405 | * @param Table $table |
||
406 | * |
||
407 | * @return void |
||
408 | */ |
||
409 | 279 | private function gatherColumns($class, Table $table) |
|
410 | { |
||
411 | 279 | $pkColumns = []; |
|
412 | |||
413 | 279 | foreach ($class->fieldMappings as $mapping) { |
|
414 | 279 | if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) { |
|
415 | 34 | continue; |
|
416 | } |
||
417 | |||
418 | 279 | $this->gatherColumn($class, $mapping, $table); |
|
419 | |||
420 | 279 | if ($class->isIdentifier($mapping['fieldName'])) { |
|
421 | 279 | $pkColumns[] = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); |
|
422 | } |
||
423 | } |
||
424 | 279 | } |
|
425 | |||
426 | /** |
||
427 | * Creates a column definition as required by the DBAL from an ORM field mapping definition. |
||
428 | * |
||
429 | * @param ClassMetadata $class The class that owns the field mapping. |
||
430 | * @param array $mapping The field mapping. |
||
431 | * @param Table $table |
||
432 | * |
||
433 | * @return void |
||
434 | */ |
||
435 | 298 | private function gatherColumn($class, array $mapping, Table $table) |
|
436 | { |
||
437 | 298 | $columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); |
|
438 | 298 | $columnType = $mapping['type']; |
|
439 | |||
440 | 298 | $options = []; |
|
441 | 298 | $options['length'] = $mapping['length'] ?? null; |
|
442 | 298 | $options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true; |
|
443 | 298 | if ($class->isInheritanceTypeSingleTable() && $class->parentClasses) { |
|
444 | 7 | $options['notnull'] = false; |
|
445 | } |
||
446 | |||
447 | 298 | $options['platformOptions'] = []; |
|
448 | 298 | $options['platformOptions']['version'] = $class->isVersioned && $class->versionField === $mapping['fieldName']; |
|
449 | |||
450 | 298 | if (strtolower($columnType) === 'string' && null === $options['length']) { |
|
451 | 155 | $options['length'] = 255; |
|
452 | } |
||
453 | |||
454 | 298 | if (isset($mapping['precision'])) { |
|
455 | 296 | $options['precision'] = $mapping['precision']; |
|
456 | } |
||
457 | |||
458 | 298 | if (isset($mapping['scale'])) { |
|
459 | 296 | $options['scale'] = $mapping['scale']; |
|
460 | } |
||
461 | |||
462 | 298 | if (isset($mapping['default'])) { |
|
463 | 30 | $options['default'] = $mapping['default']; |
|
464 | } |
||
465 | |||
466 | 298 | if (isset($mapping['columnDefinition'])) { |
|
467 | 1 | $options['columnDefinition'] = $mapping['columnDefinition']; |
|
468 | } |
||
469 | |||
470 | 298 | if (isset($mapping['options'])) { |
|
471 | 3 | $knownOptions = ['comment', 'unsigned', 'fixed', 'default']; |
|
472 | |||
473 | 3 | foreach ($knownOptions as $knownOption) { |
|
474 | 3 | if (array_key_exists($knownOption, $mapping['options'])) { |
|
475 | 2 | $options[$knownOption] = $mapping['options'][$knownOption]; |
|
476 | |||
477 | 3 | unset($mapping['options'][$knownOption]); |
|
478 | } |
||
479 | } |
||
480 | |||
481 | 3 | $options['customSchemaOptions'] = $mapping['options']; |
|
482 | } |
||
483 | |||
484 | 298 | if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == [$mapping['fieldName']]) { |
|
485 | 258 | $options['autoincrement'] = true; |
|
486 | } |
||
487 | 298 | if ($class->isInheritanceTypeJoined() && $class->name !== $class->rootEntityName) { |
|
488 | 59 | $options['autoincrement'] = false; |
|
489 | } |
||
490 | |||
491 | 298 | if ($table->hasColumn($columnName)) { |
|
492 | // required in some inheritance scenarios |
||
493 | $table->changeColumn($columnName, $options); |
||
494 | } else { |
||
495 | 298 | $table->addColumn($columnName, $columnType, $options); |
|
496 | } |
||
497 | |||
498 | 298 | $isUnique = $mapping['unique'] ?? false; |
|
499 | 298 | if ($isUnique) { |
|
500 | 18 | $table->addUniqueIndex([$columnName]); |
|
501 | } |
||
502 | 298 | } |
|
503 | |||
504 | /** |
||
505 | * Gathers the SQL for properly setting up the relations of the given class. |
||
506 | * This includes the SQL for foreign key constraints and join tables. |
||
507 | * |
||
508 | * @param ClassMetadata $class |
||
509 | * @param Table $table |
||
510 | * @param Schema $schema |
||
511 | * @param array $addedFks |
||
512 | * @param array $blacklistedFks |
||
513 | * |
||
514 | * @return void |
||
515 | * |
||
516 | * @throws \Doctrine\ORM\ORMException |
||
517 | */ |
||
518 | 298 | private function gatherRelationsSql($class, $table, $schema, &$addedFks, &$blacklistedFks) |
|
519 | { |
||
520 | 298 | foreach ($class->associationMappings as $id => $mapping) { |
|
521 | 207 | if (isset($mapping['inherited']) && ! \in_array($id, $class->identifier, true)) { |
|
522 | 21 | continue; |
|
523 | } |
||
524 | |||
525 | 207 | $foreignClass = $this->em->getClassMetadata($mapping['targetEntity']); |
|
526 | |||
527 | 207 | if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { |
|
528 | 185 | $primaryKeyColumns = []; // PK is unnecessary for this relation-type |
|
529 | |||
530 | 185 | $this->gatherRelationJoinColumns( |
|
531 | 185 | $mapping['joinColumns'], |
|
532 | 185 | $table, |
|
533 | 185 | $foreignClass, |
|
534 | 185 | $mapping, |
|
535 | 185 | $primaryKeyColumns, |
|
536 | 185 | $addedFks, |
|
537 | 185 | $blacklistedFks |
|
538 | ); |
||
539 | 151 | } elseif ($mapping['type'] == ClassMetadata::ONE_TO_MANY && $mapping['isOwningSide']) { |
|
540 | //... create join table, one-many through join table supported later |
||
541 | throw ORMException::notSupported(); |
||
542 | 151 | } elseif ($mapping['type'] == ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) { |
|
543 | // create join table |
||
544 | 47 | $joinTable = $mapping['joinTable']; |
|
545 | |||
546 | 47 | $theJoinTable = $schema->createTable( |
|
547 | 47 | $this->quoteStrategy->getJoinTableName($mapping, $foreignClass, $this->platform) |
|
548 | ); |
||
549 | |||
550 | 47 | $primaryKeyColumns = []; |
|
551 | |||
552 | // Build first FK constraint (relation table => source table) |
||
553 | 47 | $this->gatherRelationJoinColumns( |
|
554 | 47 | $joinTable['joinColumns'], |
|
555 | 47 | $theJoinTable, |
|
556 | 47 | $class, |
|
557 | 47 | $mapping, |
|
558 | 47 | $primaryKeyColumns, |
|
559 | 47 | $addedFks, |
|
560 | 47 | $blacklistedFks |
|
561 | ); |
||
562 | |||
563 | // Build second FK constraint (relation table => target table) |
||
564 | 47 | $this->gatherRelationJoinColumns( |
|
565 | 47 | $joinTable['inverseJoinColumns'], |
|
566 | 47 | $theJoinTable, |
|
567 | 47 | $foreignClass, |
|
568 | 47 | $mapping, |
|
569 | 47 | $primaryKeyColumns, |
|
570 | 47 | $addedFks, |
|
571 | 47 | $blacklistedFks |
|
572 | ); |
||
573 | |||
574 | 207 | $theJoinTable->setPrimaryKey($primaryKeyColumns); |
|
575 | } |
||
576 | } |
||
577 | 298 | } |
|
578 | |||
579 | /** |
||
580 | * Gets the class metadata that is responsible for the definition of the referenced column name. |
||
581 | * |
||
582 | * Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its |
||
583 | * not a simple field, go through all identifier field names that are associations recursively and |
||
584 | * find that referenced column name. |
||
585 | * |
||
586 | * TODO: Is there any way to make this code more pleasing? |
||
587 | * |
||
588 | * @param ClassMetadata $class |
||
589 | * @param string $referencedColumnName |
||
590 | * |
||
591 | * @return array (ClassMetadata, referencedFieldName) |
||
592 | */ |
||
593 | 207 | private function getDefiningClass($class, $referencedColumnName) |
|
594 | { |
||
595 | 207 | $referencedFieldName = $class->getFieldName($referencedColumnName); |
|
596 | |||
597 | 207 | if ($class->hasField($referencedFieldName)) { |
|
598 | 207 | return [$class, $referencedFieldName]; |
|
599 | } |
||
600 | |||
601 | 9 | if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) { |
|
602 | // it seems to be an entity as foreign key |
||
603 | 9 | foreach ($class->getIdentifierFieldNames() as $fieldName) { |
|
604 | 9 | if ($class->hasAssociation($fieldName) |
|
605 | 9 | && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) { |
|
606 | 9 | return $this->getDefiningClass( |
|
607 | 9 | $this->em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']), |
|
608 | 9 | $class->getSingleAssociationReferencedJoinColumnName($fieldName) |
|
609 | ); |
||
610 | } |
||
611 | } |
||
612 | } |
||
613 | |||
614 | return null; |
||
615 | } |
||
616 | |||
617 | /** |
||
618 | * Gathers columns and fk constraints that are required for one part of relationship. |
||
619 | * |
||
620 | * @param array $joinColumns |
||
621 | * @param Table $theJoinTable |
||
622 | * @param ClassMetadata $class |
||
623 | * @param array $mapping |
||
624 | * @param array $primaryKeyColumns |
||
625 | * @param array $addedFks |
||
626 | * @param array $blacklistedFks |
||
627 | * |
||
628 | * @return void |
||
629 | * |
||
630 | * @throws \Doctrine\ORM\ORMException |
||
631 | */ |
||
632 | 207 | private function gatherRelationJoinColumns( |
|
633 | $joinColumns, |
||
634 | $theJoinTable, |
||
635 | $class, |
||
636 | $mapping, |
||
637 | &$primaryKeyColumns, |
||
638 | &$addedFks, |
||
639 | &$blacklistedFks |
||
640 | ) |
||
641 | { |
||
642 | 207 | $localColumns = []; |
|
643 | 207 | $foreignColumns = []; |
|
644 | 207 | $fkOptions = []; |
|
645 | 207 | $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform); |
|
646 | 207 | $uniqueConstraints = []; |
|
647 | |||
648 | 207 | foreach ($joinColumns as $joinColumn) { |
|
649 | |||
650 | 207 | list($definingClass, $referencedFieldName) = $this->getDefiningClass( |
|
651 | 207 | $class, |
|
652 | 207 | $joinColumn['referencedColumnName'] |
|
653 | ); |
||
654 | |||
655 | 207 | if ( ! $definingClass) { |
|
656 | throw new \Doctrine\ORM\ORMException( |
||
657 | 'Column name `' . $joinColumn['referencedColumnName'] . '` referenced for relation from ' |
||
658 | . $mapping['sourceEntity'] . ' towards ' . $mapping['targetEntity'] . ' does not exist.' |
||
659 | ); |
||
660 | } |
||
661 | |||
662 | 207 | $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
663 | 207 | $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName( |
|
664 | 207 | $joinColumn, |
|
665 | 207 | $class, |
|
666 | 207 | $this->platform |
|
667 | ); |
||
668 | |||
669 | 207 | $primaryKeyColumns[] = $quotedColumnName; |
|
670 | 207 | $localColumns[] = $quotedColumnName; |
|
671 | 207 | $foreignColumns[] = $quotedRefColumnName; |
|
672 | |||
673 | 207 | if ( ! $theJoinTable->hasColumn($quotedColumnName)) { |
|
674 | // Only add the column to the table if it does not exist already. |
||
675 | // It might exist already if the foreign key is mapped into a regular |
||
676 | // property as well. |
||
677 | |||
678 | 204 | $fieldMapping = $definingClass->getFieldMapping($referencedFieldName); |
|
679 | |||
680 | 204 | $columnDef = null; |
|
681 | 204 | if (isset($joinColumn['columnDefinition'])) { |
|
682 | $columnDef = $joinColumn['columnDefinition']; |
||
683 | 204 | } elseif (isset($fieldMapping['columnDefinition'])) { |
|
684 | 1 | $columnDef = $fieldMapping['columnDefinition']; |
|
685 | } |
||
686 | |||
687 | 204 | $columnOptions = ['notnull' => false, 'columnDefinition' => $columnDef]; |
|
688 | |||
689 | 204 | if (isset($joinColumn['nullable'])) { |
|
690 | 127 | $columnOptions['notnull'] = ! $joinColumn['nullable']; |
|
691 | } |
||
692 | |||
693 | 204 | if (isset($fieldMapping['options'])) { |
|
694 | $columnOptions['options'] = $fieldMapping['options']; |
||
695 | } |
||
696 | |||
697 | 204 | if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) { |
|
698 | 3 | $columnOptions['length'] = $fieldMapping['length']; |
|
699 | 204 | } elseif ($fieldMapping['type'] == "decimal") { |
|
700 | $columnOptions['scale'] = $fieldMapping['scale']; |
||
701 | $columnOptions['precision'] = $fieldMapping['precision']; |
||
702 | } |
||
703 | |||
704 | 204 | $theJoinTable->addColumn($quotedColumnName, $fieldMapping['type'], $columnOptions); |
|
705 | } |
||
706 | |||
707 | 207 | if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) { |
|
708 | 63 | $uniqueConstraints[] = ['columns' => [$quotedColumnName]]; |
|
709 | } |
||
710 | |||
711 | 207 | if (isset($joinColumn['onDelete'])) { |
|
712 | 207 | $fkOptions['onDelete'] = $joinColumn['onDelete']; |
|
713 | } |
||
714 | } |
||
715 | |||
716 | // Prefer unique constraints over implicit simple indexes created for foreign keys. |
||
717 | // Also avoids index duplication. |
||
718 | 207 | foreach ($uniqueConstraints as $indexName => $unique) { |
|
719 | 63 | $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); |
|
720 | } |
||
721 | |||
722 | 207 | $compositeName = $theJoinTable->getName().'.'.implode('', $localColumns); |
|
723 | 207 | if (isset($addedFks[$compositeName]) |
|
724 | 1 | && ($foreignTableName != $addedFks[$compositeName]['foreignTableName'] |
|
725 | 207 | || 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns']))) |
|
726 | ) { |
||
727 | 1 | foreach ($theJoinTable->getForeignKeys() as $fkName => $key) { |
|
728 | 1 | if (0 === count(array_diff($key->getLocalColumns(), $localColumns)) |
|
729 | 1 | && (($key->getForeignTableName() != $foreignTableName) |
|
730 | 1 | || 0 < count(array_diff($key->getForeignColumns(), $foreignColumns))) |
|
731 | ) { |
||
732 | 1 | $theJoinTable->removeForeignKey($fkName); |
|
733 | 1 | break; |
|
734 | } |
||
735 | } |
||
736 | 1 | $blacklistedFks[$compositeName] = true; |
|
737 | 207 | } elseif ( ! isset($blacklistedFks[$compositeName])) { |
|
738 | 207 | $addedFks[$compositeName] = ['foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns]; |
|
739 | 207 | $theJoinTable->addUnnamedForeignKeyConstraint( |
|
740 | 207 | $foreignTableName, |
|
741 | 207 | $localColumns, |
|
742 | 207 | $foreignColumns, |
|
743 | 207 | $fkOptions |
|
744 | ); |
||
745 | } |
||
746 | 207 | } |
|
747 | |||
748 | /** |
||
749 | * Drops the database schema for the given classes. |
||
750 | * |
||
751 | * In any way when an exception is thrown it is suppressed since drop was |
||
752 | * issued for all classes of the schema and some probably just don't exist. |
||
753 | * |
||
754 | * @param array $classes |
||
755 | * |
||
756 | * @return void |
||
757 | */ |
||
758 | 6 | public function dropSchema(array $classes) |
|
767 | // ignored |
||
768 | } |
||
769 | } |
||
770 | 6 | } |
|
771 | |||
772 | /** |
||
773 | * Drops all elements in the database of the current connection. |
||
774 | * |
||
775 | * @return void |
||
776 | */ |
||
777 | public function dropDatabase() |
||
778 | { |
||
779 | $dropSchemaSql = $this->getDropDatabaseSQL(); |
||
780 | $conn = $this->em->getConnection(); |
||
781 | |||
782 | foreach ($dropSchemaSql as $sql) { |
||
783 | $conn->executeQuery($sql); |
||
784 | } |
||
785 | } |
||
786 | |||
787 | /** |
||
788 | * Gets the SQL needed to drop the database schema for the connections database. |
||
789 | * |
||
790 | * @return array |
||
791 | */ |
||
792 | public function getDropDatabaseSQL() |
||
793 | { |
||
794 | $sm = $this->em->getConnection()->getSchemaManager(); |
||
795 | $schema = $sm->createSchema(); |
||
796 | |||
797 | $visitor = new DropSchemaSqlCollector($this->platform); |
||
798 | $schema->visit($visitor); |
||
799 | |||
800 | return $visitor->getQueries(); |
||
801 | } |
||
802 | |||
803 | /** |
||
804 | * Gets SQL to drop the tables defined by the passed classes. |
||
805 | * |
||
806 | * @param array $classes |
||
807 | * |
||
808 | * @return array |
||
809 | */ |
||
810 | 6 | public function getDropSchemaSQL(array $classes) |
|
811 | { |
||
812 | 6 | $visitor = new DropSchemaSqlCollector($this->platform); |
|
813 | 6 | $schema = $this->getSchemaFromMetadata($classes); |
|
814 | |||
815 | 6 | $sm = $this->em->getConnection()->getSchemaManager(); |
|
816 | 6 | $fullSchema = $sm->createSchema(); |
|
817 | |||
818 | 6 | foreach ($fullSchema->getTables() as $table) { |
|
819 | 6 | if ( ! $schema->hasTable($table->getName())) { |
|
820 | 6 | foreach ($table->getForeignKeys() as $foreignKey) { |
|
821 | /* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */ |
||
822 | if ($schema->hasTable($foreignKey->getForeignTableName())) { |
||
823 | 6 | $visitor->acceptForeignKey($table, $foreignKey); |
|
824 | } |
||
825 | } |
||
826 | } else { |
||
827 | 6 | $visitor->acceptTable($table); |
|
828 | 6 | foreach ($table->getForeignKeys() as $foreignKey) { |
|
829 | 6 | $visitor->acceptForeignKey($table, $foreignKey); |
|
830 | } |
||
831 | } |
||
832 | } |
||
833 | |||
834 | 6 | if ($this->platform->supportsSequences()) { |
|
835 | foreach ($schema->getSequences() as $sequence) { |
||
836 | $visitor->acceptSequence($sequence); |
||
837 | } |
||
838 | |||
839 | foreach ($schema->getTables() as $table) { |
||
840 | /* @var $sequence Table */ |
||
841 | if ($table->hasPrimaryKey()) { |
||
842 | $columns = $table->getPrimaryKey()->getColumns(); |
||
843 | if (count($columns) == 1) { |
||
844 | $checkSequence = $table->getName() . '_' . $columns[0] . '_seq'; |
||
845 | if ($fullSchema->hasSequence($checkSequence)) { |
||
846 | $visitor->acceptSequence($fullSchema->getSequence($checkSequence)); |
||
847 | } |
||
848 | } |
||
849 | } |
||
850 | } |
||
851 | } |
||
852 | |||
853 | 6 | return $visitor->getQueries(); |
|
854 | } |
||
855 | |||
856 | /** |
||
857 | * Updates the database schema of the given classes by comparing the ClassMetadata |
||
858 | * instances to the current database schema that is inspected. |
||
859 | * |
||
860 | * @param array $classes |
||
861 | * @param boolean $saveMode If TRUE, only performs a partial update |
||
862 | * without dropping assets which are scheduled for deletion. |
||
863 | * |
||
864 | * @return void |
||
865 | */ |
||
866 | public function updateSchema(array $classes, $saveMode = false) |
||
867 | { |
||
868 | $updateSchemaSql = $this->getUpdateSchemaSql($classes, $saveMode); |
||
869 | $conn = $this->em->getConnection(); |
||
870 | |||
871 | foreach ($updateSchemaSql as $sql) { |
||
872 | $conn->executeQuery($sql); |
||
873 | } |
||
874 | } |
||
875 | |||
876 | /** |
||
877 | * Gets the sequence of SQL statements that need to be performed in order |
||
878 | * to bring the given class mappings in-synch with the relational schema. |
||
879 | * |
||
880 | * @param array $classes The classes to consider. |
||
881 | * @param boolean $saveMode If TRUE, only generates SQL for a partial update |
||
882 | * that does not include SQL for dropping assets which are scheduled for deletion. |
||
883 | * |
||
884 | * @return array The sequence of SQL statements. |
||
885 | */ |
||
886 | 1 | public function getUpdateSchemaSql(array $classes, $saveMode = false) |
|
901 | } |
||
902 | } |
||
903 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.