|
1 <?php |
|
2 /* |
|
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
14 * |
|
15 * This software consists of voluntary contributions made by many individuals |
|
16 * and is licensed under the LGPL. For more information, see |
|
17 * <http://www.doctrine-project.org>. |
|
18 */ |
|
19 |
|
20 namespace Doctrine\ORM\Tools; |
|
21 |
|
22 use Doctrine\ORM\ORMException, |
|
23 Doctrine\DBAL\Types\Type, |
|
24 Doctrine\ORM\EntityManager, |
|
25 Doctrine\ORM\Mapping\ClassMetadata, |
|
26 Doctrine\ORM\Internal\CommitOrderCalculator, |
|
27 Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs, |
|
28 Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; |
|
29 |
|
30 /** |
|
31 * The SchemaTool is a tool to create/drop/update database schemas based on |
|
32 * <tt>ClassMetadata</tt> class descriptors. |
|
33 * |
|
34 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL |
|
35 * @link www.doctrine-project.org |
|
36 * @since 2.0 |
|
37 * @version $Revision$ |
|
38 * @author Guilherme Blanco <guilhermeblanco@hotmail.com> |
|
39 * @author Jonathan Wage <jonwage@gmail.com> |
|
40 * @author Roman Borschel <roman@code-factory.org> |
|
41 * @author Benjamin Eberlei <kontakt@beberlei.de> |
|
42 */ |
|
43 class SchemaTool |
|
44 { |
|
45 /** |
|
46 * @var \Doctrine\ORM\EntityManager |
|
47 */ |
|
48 private $_em; |
|
49 |
|
50 /** |
|
51 * @var \Doctrine\DBAL\Platforms\AbstractPlatform |
|
52 */ |
|
53 private $_platform; |
|
54 |
|
55 /** |
|
56 * Initializes a new SchemaTool instance that uses the connection of the |
|
57 * provided EntityManager. |
|
58 * |
|
59 * @param Doctrine\ORM\EntityManager $em |
|
60 */ |
|
61 public function __construct(EntityManager $em) |
|
62 { |
|
63 $this->_em = $em; |
|
64 $this->_platform = $em->getConnection()->getDatabasePlatform(); |
|
65 } |
|
66 |
|
67 /** |
|
68 * Creates the database schema for the given array of ClassMetadata instances. |
|
69 * |
|
70 * @param array $classes |
|
71 */ |
|
72 public function createSchema(array $classes) |
|
73 { |
|
74 $createSchemaSql = $this->getCreateSchemaSql($classes); |
|
75 $conn = $this->_em->getConnection(); |
|
76 |
|
77 foreach ($createSchemaSql as $sql) { |
|
78 $conn->executeQuery($sql); |
|
79 } |
|
80 } |
|
81 |
|
82 /** |
|
83 * Gets the list of DDL statements that are required to create the database schema for |
|
84 * the given list of ClassMetadata instances. |
|
85 * |
|
86 * @param array $classes |
|
87 * @return array $sql The SQL statements needed to create the schema for the classes. |
|
88 */ |
|
89 public function getCreateSchemaSql(array $classes) |
|
90 { |
|
91 $schema = $this->getSchemaFromMetadata($classes); |
|
92 return $schema->toSql($this->_platform); |
|
93 } |
|
94 |
|
95 /** |
|
96 * Some instances of ClassMetadata don't need to be processed in the SchemaTool context. This method detects them. |
|
97 * |
|
98 * @param ClassMetadata $class |
|
99 * @param array $processedClasses |
|
100 * @return bool |
|
101 */ |
|
102 private function processingNotRequired($class, array $processedClasses) |
|
103 { |
|
104 return ( |
|
105 isset($processedClasses[$class->name]) || |
|
106 $class->isMappedSuperclass || |
|
107 ($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) |
|
108 ); |
|
109 } |
|
110 |
|
111 /** |
|
112 * From a given set of metadata classes this method creates a Schema instance. |
|
113 * |
|
114 * @param array $classes |
|
115 * @return Schema |
|
116 */ |
|
117 public function getSchemaFromMetadata(array $classes) |
|
118 { |
|
119 $processedClasses = array(); // Reminder for processed classes, used for hierarchies |
|
120 |
|
121 $sm = $this->_em->getConnection()->getSchemaManager(); |
|
122 $metadataSchemaConfig = $sm->createSchemaConfig(); |
|
123 $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); |
|
124 $schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $metadataSchemaConfig); |
|
125 |
|
126 $evm = $this->_em->getEventManager(); |
|
127 |
|
128 foreach ($classes as $class) { |
|
129 if ($this->processingNotRequired($class, $processedClasses)) { |
|
130 continue; |
|
131 } |
|
132 |
|
133 $table = $schema->createTable($class->getQuotedTableName($this->_platform)); |
|
134 |
|
135 $columns = array(); // table columns |
|
136 |
|
137 if ($class->isInheritanceTypeSingleTable()) { |
|
138 $columns = $this->_gatherColumns($class, $table); |
|
139 $this->_gatherRelationsSql($class, $table, $schema); |
|
140 |
|
141 // Add the discriminator column |
|
142 $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); |
|
143 |
|
144 // Aggregate all the information from all classes in the hierarchy |
|
145 foreach ($class->parentClasses as $parentClassName) { |
|
146 // Parent class information is already contained in this class |
|
147 $processedClasses[$parentClassName] = true; |
|
148 } |
|
149 |
|
150 foreach ($class->subClasses as $subClassName) { |
|
151 $subClass = $this->_em->getClassMetadata($subClassName); |
|
152 $this->_gatherColumns($subClass, $table); |
|
153 $this->_gatherRelationsSql($subClass, $table, $schema); |
|
154 $processedClasses[$subClassName] = true; |
|
155 } |
|
156 } else if ($class->isInheritanceTypeJoined()) { |
|
157 // Add all non-inherited fields as columns |
|
158 $pkColumns = array(); |
|
159 foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
160 if ( ! isset($mapping['inherited'])) { |
|
161 $columnName = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform); |
|
162 $this->_gatherColumn($class, $mapping, $table); |
|
163 |
|
164 if ($class->isIdentifier($fieldName)) { |
|
165 $pkColumns[] = $columnName; |
|
166 } |
|
167 } |
|
168 } |
|
169 |
|
170 $this->_gatherRelationsSql($class, $table, $schema); |
|
171 |
|
172 // Add the discriminator column only to the root table |
|
173 if ($class->name == $class->rootEntityName) { |
|
174 $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); |
|
175 } else { |
|
176 // Add an ID FK column to child tables |
|
177 /* @var Doctrine\ORM\Mapping\ClassMetadata $class */ |
|
178 $idMapping = $class->fieldMappings[$class->identifier[0]]; |
|
179 $this->_gatherColumn($class, $idMapping, $table); |
|
180 $columnName = $class->getQuotedColumnName($class->identifier[0], $this->_platform); |
|
181 // TODO: This seems rather hackish, can we optimize it? |
|
182 $table->getColumn($columnName)->setAutoincrement(false); |
|
183 |
|
184 $pkColumns[] = $columnName; |
|
185 |
|
186 // Add a FK constraint on the ID column |
|
187 $table->addUnnamedForeignKeyConstraint( |
|
188 $this->_em->getClassMetadata($class->rootEntityName)->getQuotedTableName($this->_platform), |
|
189 array($columnName), array($columnName), array('onDelete' => 'CASCADE') |
|
190 ); |
|
191 } |
|
192 |
|
193 $table->setPrimaryKey($pkColumns); |
|
194 |
|
195 } else if ($class->isInheritanceTypeTablePerClass()) { |
|
196 throw ORMException::notSupported(); |
|
197 } else { |
|
198 $this->_gatherColumns($class, $table); |
|
199 $this->_gatherRelationsSql($class, $table, $schema); |
|
200 } |
|
201 |
|
202 $pkColumns = array(); |
|
203 foreach ($class->identifier AS $identifierField) { |
|
204 if (isset($class->fieldMappings[$identifierField])) { |
|
205 $pkColumns[] = $class->getQuotedColumnName($identifierField, $this->_platform); |
|
206 } else if (isset($class->associationMappings[$identifierField])) { |
|
207 /* @var $assoc \Doctrine\ORM\Mapping\OneToOne */ |
|
208 $assoc = $class->associationMappings[$identifierField]; |
|
209 foreach ($assoc['joinColumns'] AS $joinColumn) { |
|
210 $pkColumns[] = $joinColumn['name']; |
|
211 } |
|
212 } |
|
213 } |
|
214 if (!$table->hasIndex('primary')) { |
|
215 $table->setPrimaryKey($pkColumns); |
|
216 } |
|
217 |
|
218 if (isset($class->table['indexes'])) { |
|
219 foreach ($class->table['indexes'] AS $indexName => $indexData) { |
|
220 $table->addIndex($indexData['columns'], $indexName); |
|
221 } |
|
222 } |
|
223 |
|
224 if (isset($class->table['uniqueConstraints'])) { |
|
225 foreach ($class->table['uniqueConstraints'] AS $indexName => $indexData) { |
|
226 $table->addUniqueIndex($indexData['columns'], $indexName); |
|
227 } |
|
228 } |
|
229 |
|
230 $processedClasses[$class->name] = true; |
|
231 |
|
232 if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) { |
|
233 $seqDef = $class->sequenceGeneratorDefinition; |
|
234 |
|
235 if (!$schema->hasSequence($seqDef['sequenceName'])) { |
|
236 $schema->createSequence( |
|
237 $seqDef['sequenceName'], |
|
238 $seqDef['allocationSize'], |
|
239 $seqDef['initialValue'] |
|
240 ); |
|
241 } |
|
242 } |
|
243 |
|
244 if ($evm->hasListeners(ToolEvents::postGenerateSchemaTable)) { |
|
245 $evm->dispatchEvent(ToolEvents::postGenerateSchemaTable, new GenerateSchemaTableEventArgs($class, $schema, $table)); |
|
246 } |
|
247 } |
|
248 |
|
249 if ($evm->hasListeners(ToolEvents::postGenerateSchema)) { |
|
250 $evm->dispatchEvent(ToolEvents::postGenerateSchema, new GenerateSchemaEventArgs($this->_em, $schema)); |
|
251 } |
|
252 |
|
253 return $schema; |
|
254 } |
|
255 |
|
256 /** |
|
257 * Gets a portable column definition as required by the DBAL for the discriminator |
|
258 * column of a class. |
|
259 * |
|
260 * @param ClassMetadata $class |
|
261 * @return array The portable column definition of the discriminator column as required by |
|
262 * the DBAL. |
|
263 */ |
|
264 private function _getDiscriminatorColumnDefinition($class, $table) |
|
265 { |
|
266 $discrColumn = $class->discriminatorColumn; |
|
267 |
|
268 if (!isset($discrColumn['type']) || (strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null)) { |
|
269 $discrColumn['type'] = 'string'; |
|
270 $discrColumn['length'] = 255; |
|
271 } |
|
272 |
|
273 $table->addColumn( |
|
274 $discrColumn['name'], |
|
275 $discrColumn['type'], |
|
276 array('length' => $discrColumn['length'], 'notnull' => true) |
|
277 ); |
|
278 } |
|
279 |
|
280 /** |
|
281 * Gathers the column definitions as required by the DBAL of all field mappings |
|
282 * found in the given class. |
|
283 * |
|
284 * @param ClassMetadata $class |
|
285 * @param Table $table |
|
286 * @return array The list of portable column definitions as required by the DBAL. |
|
287 */ |
|
288 private function _gatherColumns($class, $table) |
|
289 { |
|
290 $columns = array(); |
|
291 $pkColumns = array(); |
|
292 |
|
293 foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
294 if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) { |
|
295 continue; |
|
296 } |
|
297 |
|
298 $column = $this->_gatherColumn($class, $mapping, $table); |
|
299 |
|
300 if ($class->isIdentifier($mapping['fieldName'])) { |
|
301 $pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform); |
|
302 } |
|
303 } |
|
304 |
|
305 // For now, this is a hack required for single table inheritence, since this method is called |
|
306 // twice by single table inheritence relations |
|
307 if(!$table->hasIndex('primary')) { |
|
308 //$table->setPrimaryKey($pkColumns); |
|
309 } |
|
310 |
|
311 return $columns; |
|
312 } |
|
313 |
|
314 /** |
|
315 * Creates a column definition as required by the DBAL from an ORM field mapping definition. |
|
316 * |
|
317 * @param ClassMetadata $class The class that owns the field mapping. |
|
318 * @param array $mapping The field mapping. |
|
319 * @param Table $table |
|
320 * @return array The portable column definition as required by the DBAL. |
|
321 */ |
|
322 private function _gatherColumn($class, array $mapping, $table) |
|
323 { |
|
324 $columnName = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform); |
|
325 $columnType = $mapping['type']; |
|
326 |
|
327 $options = array(); |
|
328 $options['length'] = isset($mapping['length']) ? $mapping['length'] : null; |
|
329 $options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true; |
|
330 if ($class->isInheritanceTypeSingleTable() && count($class->parentClasses) > 0) { |
|
331 $options['notnull'] = false; |
|
332 } |
|
333 |
|
334 $options['platformOptions'] = array(); |
|
335 $options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false; |
|
336 |
|
337 if(strtolower($columnType) == 'string' && $options['length'] === null) { |
|
338 $options['length'] = 255; |
|
339 } |
|
340 |
|
341 if (isset($mapping['precision'])) { |
|
342 $options['precision'] = $mapping['precision']; |
|
343 } |
|
344 |
|
345 if (isset($mapping['scale'])) { |
|
346 $options['scale'] = $mapping['scale']; |
|
347 } |
|
348 |
|
349 if (isset($mapping['default'])) { |
|
350 $options['default'] = $mapping['default']; |
|
351 } |
|
352 |
|
353 if (isset($mapping['columnDefinition'])) { |
|
354 $options['columnDefinition'] = $mapping['columnDefinition']; |
|
355 } |
|
356 |
|
357 if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) { |
|
358 $options['autoincrement'] = true; |
|
359 } |
|
360 if ($class->isInheritanceTypeJoined() && $class->name != $class->rootEntityName) { |
|
361 $options['autoincrement'] = false; |
|
362 } |
|
363 |
|
364 if ($table->hasColumn($columnName)) { |
|
365 // required in some inheritance scenarios |
|
366 $table->changeColumn($columnName, $options); |
|
367 } else { |
|
368 $table->addColumn($columnName, $columnType, $options); |
|
369 } |
|
370 |
|
371 $isUnique = isset($mapping['unique']) ? $mapping['unique'] : false; |
|
372 if ($isUnique) { |
|
373 $table->addUniqueIndex(array($columnName)); |
|
374 } |
|
375 } |
|
376 |
|
377 /** |
|
378 * Gathers the SQL for properly setting up the relations of the given class. |
|
379 * This includes the SQL for foreign key constraints and join tables. |
|
380 * |
|
381 * @param ClassMetadata $class |
|
382 * @param \Doctrine\DBAL\Schema\Table $table |
|
383 * @param \Doctrine\DBAL\Schema\Schema $schema |
|
384 * @return void |
|
385 */ |
|
386 private function _gatherRelationsSql($class, $table, $schema) |
|
387 { |
|
388 foreach ($class->associationMappings as $fieldName => $mapping) { |
|
389 if (isset($mapping['inherited'])) { |
|
390 continue; |
|
391 } |
|
392 |
|
393 $foreignClass = $this->_em->getClassMetadata($mapping['targetEntity']); |
|
394 |
|
395 if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { |
|
396 $primaryKeyColumns = $uniqueConstraints = array(); // PK is unnecessary for this relation-type |
|
397 |
|
398 $this->_gatherRelationJoinColumns($mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); |
|
399 |
|
400 foreach($uniqueConstraints AS $indexName => $unique) { |
|
401 $table->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); |
|
402 } |
|
403 } else if ($mapping['type'] == ClassMetadata::ONE_TO_MANY && $mapping['isOwningSide']) { |
|
404 //... create join table, one-many through join table supported later |
|
405 throw ORMException::notSupported(); |
|
406 } else if ($mapping['type'] == ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) { |
|
407 // create join table |
|
408 $joinTable = $mapping['joinTable']; |
|
409 |
|
410 $theJoinTable = $schema->createTable($foreignClass->getQuotedJoinTableName($mapping, $this->_platform)); |
|
411 |
|
412 $primaryKeyColumns = $uniqueConstraints = array(); |
|
413 |
|
414 // Build first FK constraint (relation table => source table) |
|
415 $this->_gatherRelationJoinColumns($joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $uniqueConstraints); |
|
416 |
|
417 // Build second FK constraint (relation table => target table) |
|
418 $this->_gatherRelationJoinColumns($joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); |
|
419 |
|
420 $theJoinTable->setPrimaryKey($primaryKeyColumns); |
|
421 |
|
422 foreach($uniqueConstraints AS $indexName => $unique) { |
|
423 $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); |
|
424 } |
|
425 } |
|
426 } |
|
427 } |
|
428 |
|
429 /** |
|
430 * Get the class metadata that is responsible for the definition of the referenced column name. |
|
431 * |
|
432 * Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its |
|
433 * not a simple field, go through all identifier field names that are associations recursivly and |
|
434 * find that referenced column name. |
|
435 * |
|
436 * TODO: Is there any way to make this code more pleasing? |
|
437 * |
|
438 * @param ClassMetadata $class |
|
439 * @param string $referencedColumnName |
|
440 * @return array(ClassMetadata, referencedFieldName) |
|
441 */ |
|
442 private function getDefiningClass($class, $referencedColumnName) |
|
443 { |
|
444 $referencedFieldName = $class->getFieldName($referencedColumnName); |
|
445 |
|
446 if ($class->hasField($referencedFieldName)) { |
|
447 return array($class, $referencedFieldName); |
|
448 } else if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) { |
|
449 // it seems to be an entity as foreign key |
|
450 foreach ($class->getIdentifierFieldNames() AS $fieldName) { |
|
451 if ($class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) { |
|
452 return $this->getDefiningClass( |
|
453 $this->_em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']), |
|
454 $class->getSingleAssociationReferencedJoinColumnName($fieldName) |
|
455 ); |
|
456 } |
|
457 } |
|
458 } |
|
459 |
|
460 return null; |
|
461 } |
|
462 |
|
463 /** |
|
464 * Gather columns and fk constraints that are required for one part of relationship. |
|
465 * |
|
466 * @param array $joinColumns |
|
467 * @param \Doctrine\DBAL\Schema\Table $theJoinTable |
|
468 * @param ClassMetadata $class |
|
469 * @param array $mapping |
|
470 * @param array $primaryKeyColumns |
|
471 * @param array $uniqueConstraints |
|
472 */ |
|
473 private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints) |
|
474 { |
|
475 $localColumns = array(); |
|
476 $foreignColumns = array(); |
|
477 $fkOptions = array(); |
|
478 $foreignTableName = $class->getQuotedTableName($this->_platform); |
|
479 |
|
480 foreach ($joinColumns as $joinColumn) { |
|
481 $columnName = $joinColumn['name']; |
|
482 list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']); |
|
483 |
|
484 if (!$definingClass) { |
|
485 throw new \Doctrine\ORM\ORMException( |
|
486 "Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ". |
|
487 $mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist." |
|
488 ); |
|
489 } |
|
490 |
|
491 $primaryKeyColumns[] = $columnName; |
|
492 $localColumns[] = $columnName; |
|
493 $foreignColumns[] = $joinColumn['referencedColumnName']; |
|
494 |
|
495 if ( ! $theJoinTable->hasColumn($joinColumn['name'])) { |
|
496 // Only add the column to the table if it does not exist already. |
|
497 // It might exist already if the foreign key is mapped into a regular |
|
498 // property as well. |
|
499 |
|
500 $fieldMapping = $definingClass->getFieldMapping($referencedFieldName); |
|
501 |
|
502 $columnDef = null; |
|
503 if (isset($joinColumn['columnDefinition'])) { |
|
504 $columnDef = $joinColumn['columnDefinition']; |
|
505 } else if (isset($fieldMapping['columnDefinition'])) { |
|
506 $columnDef = $fieldMapping['columnDefinition']; |
|
507 } |
|
508 $columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef); |
|
509 if (isset($joinColumn['nullable'])) { |
|
510 $columnOptions['notnull'] = !$joinColumn['nullable']; |
|
511 } |
|
512 if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) { |
|
513 $columnOptions['length'] = $fieldMapping['length']; |
|
514 } else if ($fieldMapping['type'] == "decimal") { |
|
515 $columnOptions['scale'] = $fieldMapping['scale']; |
|
516 $columnOptions['precision'] = $fieldMapping['precision']; |
|
517 } |
|
518 |
|
519 $theJoinTable->addColumn($columnName, $fieldMapping['type'], $columnOptions); |
|
520 } |
|
521 |
|
522 if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) { |
|
523 $uniqueConstraints[] = array('columns' => array($columnName)); |
|
524 } |
|
525 |
|
526 if (isset($joinColumn['onUpdate'])) { |
|
527 $fkOptions['onUpdate'] = $joinColumn['onUpdate']; |
|
528 } |
|
529 |
|
530 if (isset($joinColumn['onDelete'])) { |
|
531 $fkOptions['onDelete'] = $joinColumn['onDelete']; |
|
532 } |
|
533 } |
|
534 |
|
535 $theJoinTable->addUnnamedForeignKeyConstraint( |
|
536 $foreignTableName, $localColumns, $foreignColumns, $fkOptions |
|
537 ); |
|
538 } |
|
539 |
|
540 /** |
|
541 * Drops the database schema for the given classes. |
|
542 * |
|
543 * In any way when an exception is thrown it is supressed since drop was |
|
544 * issued for all classes of the schema and some probably just don't exist. |
|
545 * |
|
546 * @param array $classes |
|
547 * @return void |
|
548 */ |
|
549 public function dropSchema(array $classes) |
|
550 { |
|
551 $dropSchemaSql = $this->getDropSchemaSQL($classes); |
|
552 $conn = $this->_em->getConnection(); |
|
553 |
|
554 foreach ($dropSchemaSql as $sql) { |
|
555 try { |
|
556 $conn->executeQuery($sql); |
|
557 } catch(\Exception $e) { |
|
558 |
|
559 } |
|
560 } |
|
561 } |
|
562 |
|
563 /** |
|
564 * Drops all elements in the database of the current connection. |
|
565 * |
|
566 * @return void |
|
567 */ |
|
568 public function dropDatabase() |
|
569 { |
|
570 $dropSchemaSql = $this->getDropDatabaseSQL(); |
|
571 $conn = $this->_em->getConnection(); |
|
572 |
|
573 foreach ($dropSchemaSql as $sql) { |
|
574 $conn->executeQuery($sql); |
|
575 } |
|
576 } |
|
577 |
|
578 /** |
|
579 * Gets the SQL needed to drop the database schema for the connections database. |
|
580 * |
|
581 * @return array |
|
582 */ |
|
583 public function getDropDatabaseSQL() |
|
584 { |
|
585 $sm = $this->_em->getConnection()->getSchemaManager(); |
|
586 $schema = $sm->createSchema(); |
|
587 |
|
588 $visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->_platform); |
|
589 /* @var $schema \Doctrine\DBAL\Schema\Schema */ |
|
590 $schema->visit($visitor); |
|
591 return $visitor->getQueries(); |
|
592 } |
|
593 |
|
594 /** |
|
595 * Get SQL to drop the tables defined by the passed classes. |
|
596 * |
|
597 * @param array $classes |
|
598 * @return array |
|
599 */ |
|
600 public function getDropSchemaSQL(array $classes) |
|
601 { |
|
602 $visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->_platform); |
|
603 $schema = $this->getSchemaFromMetadata($classes); |
|
604 |
|
605 $sm = $this->_em->getConnection()->getSchemaManager(); |
|
606 $fullSchema = $sm->createSchema(); |
|
607 foreach ($fullSchema->getTables() AS $table) { |
|
608 if (!$schema->hasTable($table->getName())) { |
|
609 foreach ($table->getForeignKeys() AS $foreignKey) { |
|
610 /* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */ |
|
611 if ($schema->hasTable($foreignKey->getForeignTableName())) { |
|
612 $visitor->acceptForeignKey($table, $foreignKey); |
|
613 } |
|
614 } |
|
615 } else { |
|
616 $visitor->acceptTable($table); |
|
617 foreach ($table->getForeignKeys() AS $foreignKey) { |
|
618 $visitor->acceptForeignKey($table, $foreignKey); |
|
619 } |
|
620 } |
|
621 } |
|
622 |
|
623 if ($this->_platform->supportsSequences()) { |
|
624 foreach ($schema->getSequences() AS $sequence) { |
|
625 $visitor->acceptSequence($sequence); |
|
626 } |
|
627 foreach ($schema->getTables() AS $table) { |
|
628 /* @var $sequence Table */ |
|
629 if ($table->hasPrimaryKey()) { |
|
630 $columns = $table->getPrimaryKey()->getColumns(); |
|
631 if (count($columns) == 1) { |
|
632 $checkSequence = $table->getName() . "_" . $columns[0] . "_seq"; |
|
633 if ($fullSchema->hasSequence($checkSequence)) { |
|
634 $visitor->acceptSequence($fullSchema->getSequence($checkSequence)); |
|
635 } |
|
636 } |
|
637 } |
|
638 } |
|
639 } |
|
640 |
|
641 return $visitor->getQueries(); |
|
642 } |
|
643 |
|
644 /** |
|
645 * Updates the database schema of the given classes by comparing the ClassMetadata |
|
646 * instances to the current database schema that is inspected. If $saveMode is set |
|
647 * to true the command is executed in the Database, else SQL is returned. |
|
648 * |
|
649 * @param array $classes |
|
650 * @param boolean $saveMode |
|
651 * @return void |
|
652 */ |
|
653 public function updateSchema(array $classes, $saveMode=false) |
|
654 { |
|
655 $updateSchemaSql = $this->getUpdateSchemaSql($classes, $saveMode); |
|
656 $conn = $this->_em->getConnection(); |
|
657 |
|
658 foreach ($updateSchemaSql as $sql) { |
|
659 $conn->executeQuery($sql); |
|
660 } |
|
661 } |
|
662 |
|
663 /** |
|
664 * Gets the sequence of SQL statements that need to be performed in order |
|
665 * to bring the given class mappings in-synch with the relational schema. |
|
666 * If $saveMode is set to true the command is executed in the Database, |
|
667 * else SQL is returned. |
|
668 * |
|
669 * @param array $classes The classes to consider. |
|
670 * @param boolean $saveMode True for writing to DB, false for SQL string |
|
671 * @return array The sequence of SQL statements. |
|
672 */ |
|
673 public function getUpdateSchemaSql(array $classes, $saveMode=false) |
|
674 { |
|
675 $sm = $this->_em->getConnection()->getSchemaManager(); |
|
676 |
|
677 $fromSchema = $sm->createSchema(); |
|
678 $toSchema = $this->getSchemaFromMetadata($classes); |
|
679 |
|
680 $comparator = new \Doctrine\DBAL\Schema\Comparator(); |
|
681 $schemaDiff = $comparator->compare($fromSchema, $toSchema); |
|
682 |
|
683 if ($saveMode) { |
|
684 return $schemaDiff->toSaveSql($this->_platform); |
|
685 } else { |
|
686 return $schemaDiff->toSql($this->_platform); |
|
687 } |
|
688 } |
|
689 } |