From f3c0d8c5209b963dc04a9682371509d0bb16fb46 Mon Sep 17 00:00:00 2001 From: Tyler Sommer Date: Thu, 16 Feb 2012 20:27:08 -0700 Subject: [PATCH 1/2] Continued development. Implemented all parts of a SchemaDiff except for dropping foreign keys. --- .../Migrations/Generator/MigrationGenerator.php | 318 +++++++++++++++++--- .../Tools/Console/Command/DiffCommand.php | 4 +- 2 files changed, 270 insertions(+), 52 deletions(-) diff --git a/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php b/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php index b32683e..1d2d4a2 100644 --- a/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php +++ b/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php @@ -1,123 +1,341 @@ . + */ + namespace Doctrine\DBAL\Migrations\Generator; -use Doctrine\DBAL\Schema\SchemaDiff, - Doctrine\DBAL\Schema\AbstractAsset; +use Doctrine\DBAL\Migrations\Configuration\Configuration, + Doctrine\DBAL\Schema\SchemaDiff, + Doctrine\DBAL\Schema; +/** + * Generates the body of a Migration by iterating through the changes described + * by a SchemaDiff object. + * + * @author Tyler Sommer + */ class MigrationGenerator { /** + * @var Doctrine\DBAL\Migrations\Configuration\Configuration + */ + protected $_configuration; + + /** * @var \ReflectionProperty A Reflection on Doctrine\DBAL\Schema\AbstractAsset#$_quoted */ protected $_assetQuotedProperty; /** - * @var \ReflectionProperty A Reflection on Doctrine\DBAL\Schema\Column#$_options + * @var \ReflectionProperty A Reflection on Doctrine\DBAL\Schema\ForeignKeyConstraint#$_options */ - protected $_columnOptionsProperty; + protected $_foreignKeyOptionsProperty; /** * Constructor + * + * @param Doctrine\DBAL\Migrations\Configuration\Configuration A Migration configuration */ - public function __construct() + public function __construct(Configuration $configuration) { + $this->_configuration = $configuration; + $reflected = new \ReflectionClass('Doctrine\DBAL\Schema\AbstractAsset'); $this->_assetQuotedProperty = $reflected->getProperty('_quoted'); $this->_assetQuotedProperty->setAccessible(true); + + $reflected = new \ReflectionClass('Doctrine\DBAL\Schema\ForeignKeyConstraint'); + $this->_foreignKeyOptionsProperty = $reflected->getProperty('_options'); + $this->_foreignKeyOptionsProperty->setAccessible(true); } /** - * Generate Code From SchemaDiff - * - * Iterates the changes described by a SchemaDiff and outputs the body - * of a migration using methods on the Schema to make changes. + * Generates code using a SchemaDiff * * @param Doctrine\DBAL\Schema\SchemaDiff - * @return string Raw PHP code to be used for the body of a Migration + * @return string Raw PHP code to be used as the body of a Migration */ public function generateCodeFromSchemaDiff(SchemaDiff $schemaDiff) { - $code = array(); - - // foreach ($schemaDiff->orphanedForeignKeys as $orphanedForeignKey) { - // // Drop foreign key - // - // // Need a way to get the table that the orphanedForeignKey belonged to. - // // Currently, we can get its name but not the actual Table - // $code[] = sprintf('$table = $schema->getTable(\'%s\');', $orphanedForeignKey->getLocalTableName()); - // $code[] = sprintf('$table->dropForeignKey(\'%s\');', $orphanedForeignKey->getName()); - // } + $code = array(''); foreach ($schemaDiff->changedSequences as $sequence) { - $code[] = sprintf('$sequence = $schema->getSequence(\'%s\');', $sequence->getName()); + $code[] = sprintf('$sequence = $schema->getSequence(\'%s\');', $this->_getQuotedIdentifier($sequence)); $code[] = sprintf('$sequence->setAllocationSize(%s);', $sequence->getAllocationSize()); $code[] = sprintf('$sequence->setInitialValue(%s);', $sequence->getInitialValue()); $code[] = ''; } foreach ($schemaDiff->removedSequences as $sequence) { - $code[] = sprintf('$schema->dropSequence(\'%s\');', $sequence->getName()); + $code[] = sprintf('$schema->dropSequence(\'%s\');', $this->_getQuotedIdentifier($sequence)); $code[] = ''; } foreach ($schemaDiff->newSequences as $sequence) { - $code[] = sprintf('$schema->createSequence(\'%s\', %s, %s);', $sequence->getName(), $sequence->getAllocationSize(), $sequence->getInitialValue()); + $code[] = sprintf('$schema->createSequence(\'%s\', %s, %s);', $this->_getQuotedIdentifier($sequence), $sequence->getAllocationSize(), $sequence->getInitialValue()); $code[] = ''; } foreach ($schemaDiff->newTables as $table) { - // Need to use reflection to check if the table name is quoted - $code[] = sprintf('$table = $schema->createTable(\'%s\');', $table->getName()); + if ($table->getName() == $this->_configuration->getMigrationsTableName()) { + continue; + } + + $code[] = '// Create table: ' . $table->getName(); + $code[] = sprintf('$table = $schema->createTable(\'%s\');', $this->_getQuotedIdentifier($table)); + + foreach ($table->getOptions() as $name => $value) { + $code[] = sprintf('$table->addOption(\'%s\', \'%s\');'); + } foreach ($table->getColumns() as $column) { - // Need to use reflection to check if the column is quoted; if so, we need to quote it in the addColumn() call - $code[] = sprintf('$table->addColumn(\'%s\', \'%s\');', $this->_getQuotedIdentifer($column), $column->getType()->getName()); + $code[] = $this->_getCreateColumnCode($column); } foreach ($table->getIndexes() as $index) { - if ($index->isPrimary()) { - $str = '$table->setPrimaryKey(%s, \'%s\');'; - } - else if ($index->isUnique()) { - $str = '$table->addUniqueIndex(%s, \'%s\');'; - } - else { - $str = '$table->addIndex(%s, \'%s\');'; - } - - // Technically, we should also check if the index name is quoted too. - $code[] = sprintf($str, var_export($index->getColumns(), true), $index->getName()); + $code[] = $this->_getCreateIndexCode($index); } foreach ($table->getForeignKeys() as $foreignKey) { - // Probably need reflection to get the foreign key's $_options property - $code[] = sprintf('$table->addForeignKeyConstraint(\'%s\', %s, %s, %s, \'%s\');', - $foreignKey->getForeignTableName(), - var_export($foreignKey->getLocalColumns(), true), - var_export($foreignKey->getForeignColumns(), true), - 'array()', //var_export($foreignKey->getOptions(), true), - $foreignKey->getName() - ); + $code[] = $this->_getCreateForeignKeyCode($foreignKey); } $code[] = ''; } foreach ($schemaDiff->removedTables as $table) { - $code[] = sprintf('$table->dropTable(\'%s\');', $table->getName()); + if ($table->getName() == $this->_configuration->getMigrationsTableName()) { + continue; + } + + $code[] = '// Drop table: ' . $table->getName(); + $code[] = sprintf('$table->dropTable(\'%s\');', $this->_getQuotedIdentifier($table)); $code[] = ''; } foreach ($schemaDiff->changedTables as $tableDiff) { - // Alter table + if ($tableDiff->name == $this->_configuration->getMigrationsTableName()) { + continue; + } + + $code[] = '// Alter table: ' . $tableDiff->name; + $code[] = sprintf('$table = $schema->getTable(\'%s\');', $tableDiff->name); + + if ($tableDiff->newName !== false) { + $code[] = sprintf('$table->setName(\'%s\');', $tableDiff->newName); + } + + foreach ($tableDiff->addedColumns as $columnName => $column) { + $code[] = $this->_getCreateColumnCode($column); + } + + foreach ($tableDiff->changedColumns as $oldName => $columnDiff) { + $code[] = sprintf('$column = $table->getColumn(\'%s\');', $oldName); + foreach ($columnDiff->changedProperties as $property) { + $getter = "get{$property}"; + $value = call_user_func(array($columnDiff->column, $getter)); + + if (is_object($value)) { $value = $value->getName(); } + + $code[] = sprintf('$column->set%s(%s);', ucfirst($property), $this->_exportVar($value)); + } + } + + foreach ($tableDiff->removedColumns as $columnName => $removed) { + $code[] = sprintf('$table->dropColumn(\'%s\');', $columnName); + } + + foreach ($tableDiff->renamedColumns as $oldName => $column) { + $code[] = sprintf('$column = $table->getColumn(\'%s\');', $oldName); + $code[] = sprintf('$column->setName(\'%s\');', $column->getName()); + } + + foreach ($tableDiff->addedIndexes as $indexName => $index) { + $code[] = $this->_getCreateIndexCode($index); + } + + foreach ($tableDiff->changedIndexes as $oldName => $index) { + $code[] = sprintf('$table->dropIndex(\'%s\');', $oldName); + $code[] = $this->_getCreateIndexCode($index); + } + + foreach ($tableDiff->removedIndexes as $indexName => $removed) { + $code[] = sprintf('$table->dropIndex(\'%s\');', $indexName); + } + + foreach ($tableDiff->addedForeignKeys as $foreignKey) { + $code[] = $this->_getCreateForeignKeyCode($foreignKey); + } + + foreach ($tableDiff->changedForeignKeys as $foreignKey) { + $code[] = sprintf('$foreignKey = $table->getForeignKey(\'%s\');', $foreignKey->getName()); + $code[] = sprintf('$foreignKey->setLocalColumns(%s);', $this->_exportVar($foreignKey->getLocalColumns())); + $code[] = sprintf('$foreignKey->setForeignTableName(\'%s\');', $foreignKey->getForeignTableName()); + $code[] = sprintf('$foreignKey->setForeignColumns(%s);', $this->_exportVar($foreignKey->getForeignColumns())); + } + + foreach ($tableDiff->removedForeignKeys as $foreignKey) { + // Seems like we have to drop the table and then recreate it, doesn't seem ideal. + echo "Skipping removal of foreign key constraint: " . $foreignKey->getName() . "\n"; + } + + $code[] = ''; } return implode("\n", $code); } - protected function _getQuotedIdentifier(AbstractAsset $asset) + /** + * @param Doctrine\DBAL\Schema\Column $column + * @return string + */ + protected function _getCreateColumnCode(Schema\Column $column) + { + return sprintf('$table->addColumn(\'%s\', \'%s\', %s);', + $this->_getQuotedIdentifier($column), + $column->getType()->getName(), + $this->_exportVar($this->_getColumnOptions($column)) + ); + } + /** + * @param Doctrine\DBAL\Schema\Index $index + * @return string + */ + protected function _getCreateIndexCode(Schema\Index $index) + { + if ($index->isPrimary()) { + $str = '$table->setPrimaryKey(%s, \'%s\');'; + } + else if ($index->isUnique()) { + $str = '$table->addUniqueIndex(%s, \'%s\');'; + } + else { + $str = '$table->addIndex(%s, \'%s\');'; + } + + return sprintf($str, $this->_exportVar($index->getColumns()), $this->_getQuotedIdentifier($index)); + } + + /** + * @param Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * @return string + */ + protected function _getCreateForeignKeyCode(Schema\ForeignKeyConstraint $foreignKey) + { + return sprintf('$table->addForeignKeyConstraint(\'%s\', %s, %s, %s, \'%s\');', + $foreignKey->getForeignTableName(), + $this->_exportVar($foreignKey->getLocalColumns()), + $this->_exportVar($foreignKey->getForeignColumns()), + $this->_exportVar($this->_getForeignKeyOptions($foreignKey)), + $this->_getQuotedIdentifier($foreignKey) + ); + } + + /** + * @param Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * @return array + */ + protected function _getForeignKeyOptions(Schema\ForeignKeyConstraint $foreignKey) + { + return $this->_foreignKeyOptionsProperty->getValue($foreignKey); + } + + /** + * @param Doctrine\DBAL\Schema\Column $column + * @return array + */ + protected function _getColumnOptions(Schema\Column $column) { + $options = array(); + + if ($column->getLength() !== null) { + $options['length'] = $column->getLength(); + } + + if ($column->getPrecision() !== 0) { + $options['precision'] = $column->getPrecision(); + } + + if ($column->getScale() !== 0) { + $options['scale'] = $column->getScale(); + } + + if ($column->getUnsigned() !== false) { + $options['unsigned'] = true; + } + + if ($column->getFixed() !== false) { + $options['fixed'] = true; + } + + if ($column->getNotNull() !== true) { + $options['notNull'] = false; + } + + if ($column->getDefault() !== null) { + $options['default'] = $column->getDefault(); + } + + if ($column->getColumnDefinition() !== null) { + $options['columnDefinition'] = $column->getColumnDefinition(); + } + + if ($column->getPlatformOptions() !== array('version' => false) && $column->getPlatformOptions() !== array()) { + $options['platformOptions'] = $column->getPlatformOptions(); + } + + if ($column->getAutoincrement() !== false) { + $options['autoincrement'] = $column->getAutoincrement(); + } + + if ($column->getComment() !== null) { + $options['comment'] = $column->getComment(); + } + + return $options; + } + + /** + * @param Doctrine\DBAL\Schema\AbstractAsset $asset + * @return string + */ + protected function _getQuotedIdentifier(Schema\AbstractAsset $asset) { return $this->_assetQuotedProperty->getValue($asset) ? '"' . $asset->getName() . '"' : $asset->getName(); } + + /** + * @param mixed $var + * @return string + */ + protected function _exportVar($var) + { + $export = var_export($var, true); + + if (is_array($var)) { + $export = preg_replace('/[0-9+] \=\> /', '', $export); + $export = str_replace(array("array (\n)", "\n ", 'array ('), array('array()', "\n ", 'array('), $export); + + if (count($var) == 1) { + $export = str_replace(array("\n", ' ', ',)'), array('', '', ')'), $export); + } + } + + return $export; + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Migrations/Tools/Console/Command/DiffCommand.php b/lib/Doctrine/DBAL/Migrations/Tools/Console/Command/DiffCommand.php index c491a30..33fb5e8 100644 --- a/lib/Doctrine/DBAL/Migrations/Tools/Console/Command/DiffCommand.php +++ b/lib/Doctrine/DBAL/Migrations/Tools/Console/Command/DiffCommand.php @@ -28,7 +28,7 @@ use Symfony\Component\Console\Input\InputInterface, use Doctrine\DBAL\Schema\Comparator, Doctrine\DBAL\Schema\SchemaDiff, - Doctrine\DBAL\Generator\MigrationGenerator; + Doctrine\DBAL\Migrations\Generator\MigrationGenerator; /** * Command for generate migration classes by comparing your current database schema @@ -103,7 +103,7 @@ EOT private function buildCode(Configuration $configuration, SchemaDiff $schemaDiff) { - $generator = new MigrationGenerator(); + $generator = new MigrationGenerator($configuration); return $generator->generateCodeFromSchemaDiff($schemaDiff); } -- 1.7.3.5 From cb9b8bd2f81889a57a2334bb26208a2dad9517c6 Mon Sep 17 00:00:00 2001 From: Tyler Sommer Date: Thu, 16 Feb 2012 23:06:17 -0700 Subject: [PATCH 2/2] Generator now supports all of SchemaDiff's changes --- .../Migrations/Generator/MigrationGenerator.php | 62 +++++++++++++++---- 1 files changed, 49 insertions(+), 13 deletions(-) diff --git a/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php b/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php index 1d2d4a2..c45ac82 100644 --- a/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php +++ b/lib/Doctrine/DBAL/Migrations/Generator/MigrationGenerator.php @@ -22,7 +22,8 @@ namespace Doctrine\DBAL\Migrations\Generator; use Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Schema\SchemaDiff, - Doctrine\DBAL\Schema; + Doctrine\DBAL\Schema, + Doctrine\DBAL\Types\Type; /** * Generates the body of a Migration by iterating through the changes described @@ -125,7 +126,7 @@ class MigrationGenerator } $code[] = '// Drop table: ' . $table->getName(); - $code[] = sprintf('$table->dropTable(\'%s\');', $this->_getQuotedIdentifier($table)); + $code[] = sprintf('$schema->dropTable(\'%s\');', $this->_getQuotedIdentifier($table)); $code[] = ''; } @@ -151,9 +152,12 @@ class MigrationGenerator $getter = "get{$property}"; $value = call_user_func(array($columnDiff->column, $getter)); - if (is_object($value)) { $value = $value->getName(); } - - $code[] = sprintf('$column->set%s(%s);', ucfirst($property), $this->_exportVar($value)); + if ($value instanceof Type) { + $code[] = sprintf('$column->setType(\Doctrine\DBAL\Types\Type::getType(\'%s\'));', $value->getName()); + } + else { + $code[] = sprintf('$column->set%s(%s);', ucfirst($property), $this->_exportVar($value)); + } } } @@ -171,12 +175,12 @@ class MigrationGenerator } foreach ($tableDiff->changedIndexes as $oldName => $index) { - $code[] = sprintf('$table->dropIndex(\'%s\');', $oldName); + $code[] = $this->_getDropIndexCode($oldName); $code[] = $this->_getCreateIndexCode($index); } foreach ($tableDiff->removedIndexes as $indexName => $removed) { - $code[] = sprintf('$table->dropIndex(\'%s\');', $indexName); + $code[] = $this->_getDropIndexCode($indexName); } foreach ($tableDiff->addedForeignKeys as $foreignKey) { @@ -184,15 +188,12 @@ class MigrationGenerator } foreach ($tableDiff->changedForeignKeys as $foreignKey) { - $code[] = sprintf('$foreignKey = $table->getForeignKey(\'%s\');', $foreignKey->getName()); - $code[] = sprintf('$foreignKey->setLocalColumns(%s);', $this->_exportVar($foreignKey->getLocalColumns())); - $code[] = sprintf('$foreignKey->setForeignTableName(\'%s\');', $foreignKey->getForeignTableName()); - $code[] = sprintf('$foreignKey->setForeignColumns(%s);', $this->_exportVar($foreignKey->getForeignColumns())); + $code[] = $this->_getDropForeignKeyCode($foreignKey); + $code[] = $this->_getCreateForeignKeyCode($foreignKey); } foreach ($tableDiff->removedForeignKeys as $foreignKey) { - // Seems like we have to drop the table and then recreate it, doesn't seem ideal. - echo "Skipping removal of foreign key constraint: " . $foreignKey->getName() . "\n"; + $code[] = $this->_getDropForeignKeyCode($foreignKey); } $code[] = ''; @@ -213,6 +214,7 @@ class MigrationGenerator $this->_exportVar($this->_getColumnOptions($column)) ); } + /** * @param Doctrine\DBAL\Schema\Index $index * @return string @@ -233,6 +235,22 @@ class MigrationGenerator } /** + * @param string $indexName The name of the index to drop + * @return string + */ + protected function _getDropIndexCode($indexName) + { + return <<getProperty('_indexes'); +\$indexesProperty->setAccessible(true); +\$indexes = \$indexesProperty->getValue(\$table); +unset(\$indexes['{$indexName}']); +\$indexesProperty->setValue(\$table, \$indexes); +END; + } + + /** * @param Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey * @return string */ @@ -248,6 +266,24 @@ class MigrationGenerator } /** + * @param string $indexName The name of the index to drop + * @return string + */ + protected function _getDropForeignKeyCode(Schema\ForeignKeyConstraint $foreignKey) + { + $keyName = strtolower($foreignKey->getName()); + + return <<getProperty('_fkConstraints'); +\$fkConstraintsProperty->setAccessible(true); +\$fkConstraints = \$fkConstraintsProperty->getValue(\$table); +unset(\$fkConstraints['{$keyName}']); +\$fkConstraintsProperty->setValue(\$table, \$fkConstraints); +END; + } + + /** * @param Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey * @return array */ -- 1.7.3.5