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