From 262ae7c942ae1afa046b95958ed44a2a18f4ddbf Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 May 2011 16:36:43 +0200 Subject: [PATCH 01/10] Implemented tableName -> className and columnName -> fieldName mapping in DatabaseDriver. --- .../ORM/Mapping/Driver/DatabaseDriver.php | 95 ++++++++++++++++--- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php index 7f92df162..d41553152 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php @@ -55,7 +55,17 @@ class DatabaseDriver implements Driver * @var array */ private $manyToManyTables = array(); - + + /** + * @var array + */ + private $classNamesForTables = array(); + + /** + * @var array + */ + private $fieldNamesForColumns = array(); + /** * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading * docblock annotations. @@ -78,7 +88,7 @@ class DatabaseDriver implements Driver { $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($entityTables AS $table) { - $className = Inflector::classify(strtolower($table->getName())); + $className = $this->_getClassNameForTable($table->getName()); $this->classToTableNames[$className] = $table->getName(); $this->tables[$table->getName()] = $table; } @@ -120,7 +130,7 @@ class DatabaseDriver implements Driver } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, // assumption is lower-case + underscore separated. - $className = Inflector::classify(strtolower($tableName)); + $className = $this->_getClassNameForTable($tableName); $this->tables[$tableName] = $table; $this->classToTableNames[$className] = $tableName; } @@ -172,7 +182,7 @@ class DatabaseDriver implements Driver continue; } - $fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName())); + $fieldMapping['fieldName'] = $this->_getFieldNameForColumn($tableName, $column->getName(), false); $fieldMapping['columnName'] = $column->getName(); $fieldMapping['type'] = strtolower((string) $column->getType()); @@ -226,10 +236,10 @@ class DatabaseDriver implements Driver $localColumn = current($myFk->getColumns()); $associationMapping = array(); - $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns())))); - $associationMapping['targetEntity'] = Inflector::classify(strtolower($otherFk->getForeignTableName())); + $associationMapping['fieldName'] = $this->_getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true); + $associationMapping['targetEntity'] = $this->_getClassNameForTable($otherFk->getForeignTableName()); if (current($manyTable->getColumns())->getName() == $localColumn) { - $associationMapping['inversedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns())))); + $associationMapping['inversedBy'] = $this->_getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); $associationMapping['joinTable'] = array( 'name' => strtolower($manyTable->getName()), 'joinColumns' => array(), @@ -254,7 +264,7 @@ class DatabaseDriver implements Driver ); } } else { - $associationMapping['mappedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns())))); + $associationMapping['mappedBy'] = $this->_getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); } $metadata->mapManyToMany($associationMapping); break; @@ -269,8 +279,8 @@ class DatabaseDriver implements Driver $localColumn = current($cols); $associationMapping = array(); - $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower($localColumn))); - $associationMapping['targetEntity'] = Inflector::classify($foreignTable); + $associationMapping['fieldName'] = $this->_getFieldNameForColumn($tableName, $localColumn, true); + $associationMapping['targetEntity'] = $this->_getClassNameForTable($foreignTable); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinColumns'][] = array( @@ -303,4 +313,67 @@ class DatabaseDriver implements Driver return array_keys($this->classToTableNames); } -} \ No newline at end of file + + /** + * Set class name for a table. + * + * @param string $tableName + * @param string $className + * @return void + */ + public function setClassNameForTable($tableName, $className) + { + $this->classNamesForTables[$tableName] = $className; + } + + /** + * Set field name for a column on a specific table. + * + * @param string $tableName + * @param string $columnName + * @param string $fieldName + * @return void + */ + public function setFieldNameForColumn($tableName, $columnName, $fieldName) + { + $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; + } + + /** + * Return the mapped class name for a table if it exists. Otherwise return "classified" version. + * + * @param string $tableName + * @return string + */ + private function _getClassNameForTable($tableName) + { + if (isset($this->classNamesForTables[$tableName])) { + return $this->classNamesForTables[$tableName]; + } + + return Inflector::classify(strtolower($tableName)); + } + + /** + * Return the mapped field name for a column, if it exists. Otherwise return camelized version. + * + * @param string $tableName + * @param string $columnName + * @param boolean $fk Whether the column is a foreignkey or not. + * @return string + */ + private function _getFieldNameForColumn($tableName, $columnName, $fk = false) + { + if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) { + return $this->fieldNamesForColumns[$tableName][$columnName]; + } + + $columnName = strtolower($columnName); + + // Replace _id if it is a foreignkey column + if ($fk) { + $columnName = str_replace('_id', '', $columnName); + } + return Inflector::camelize($columnName); + } +} From cec62db2d8c6428fc1bd161bac2e221c35d3c79e Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 May 2011 16:53:35 +0200 Subject: [PATCH 02/10] Removed _ prefix from private functions. --- .../ORM/Mapping/Driver/DatabaseDriver.php | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php index d41553152..2805d5a21 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php @@ -88,7 +88,7 @@ class DatabaseDriver implements Driver { $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($entityTables AS $table) { - $className = $this->_getClassNameForTable($table->getName()); + $className = $this->getClassNameForTable($table->getName()); $this->classToTableNames[$className] = $table->getName(); $this->tables[$table->getName()] = $table; } @@ -130,7 +130,7 @@ class DatabaseDriver implements Driver } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, // assumption is lower-case + underscore separated. - $className = $this->_getClassNameForTable($tableName); + $className = $this->getClassNameForTable($tableName); $this->tables[$tableName] = $table; $this->classToTableNames[$className] = $tableName; } @@ -182,7 +182,7 @@ class DatabaseDriver implements Driver continue; } - $fieldMapping['fieldName'] = $this->_getFieldNameForColumn($tableName, $column->getName(), false); + $fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false); $fieldMapping['columnName'] = $column->getName(); $fieldMapping['type'] = strtolower((string) $column->getType()); @@ -236,10 +236,10 @@ class DatabaseDriver implements Driver $localColumn = current($myFk->getColumns()); $associationMapping = array(); - $associationMapping['fieldName'] = $this->_getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true); - $associationMapping['targetEntity'] = $this->_getClassNameForTable($otherFk->getForeignTableName()); + $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true); + $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); if (current($manyTable->getColumns())->getName() == $localColumn) { - $associationMapping['inversedBy'] = $this->_getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); + $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); $associationMapping['joinTable'] = array( 'name' => strtolower($manyTable->getName()), 'joinColumns' => array(), @@ -264,7 +264,7 @@ class DatabaseDriver implements Driver ); } } else { - $associationMapping['mappedBy'] = $this->_getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); + $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); } $metadata->mapManyToMany($associationMapping); break; @@ -279,8 +279,8 @@ class DatabaseDriver implements Driver $localColumn = current($cols); $associationMapping = array(); - $associationMapping['fieldName'] = $this->_getFieldNameForColumn($tableName, $localColumn, true); - $associationMapping['targetEntity'] = $this->_getClassNameForTable($foreignTable); + $associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true); + $associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinColumns'][] = array( @@ -345,7 +345,7 @@ class DatabaseDriver implements Driver * @param string $tableName * @return string */ - private function _getClassNameForTable($tableName) + private function getClassNameForTable($tableName) { if (isset($this->classNamesForTables[$tableName])) { return $this->classNamesForTables[$tableName]; @@ -362,7 +362,7 @@ class DatabaseDriver implements Driver * @param boolean $fk Whether the column is a foreignkey or not. * @return string */ - private function _getFieldNameForColumn($tableName, $columnName, $fk = false) + private function getFieldNameForColumn($tableName, $columnName, $fk = false) { if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) { return $this->fieldNamesForColumns[$tableName][$columnName]; From 693fc090b5bfd97c2cab97d6a327162c8d4364c1 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Wed, 25 May 2011 12:35:54 +0200 Subject: [PATCH 03/10] Updated the EntityGenerator to be compatible with Common 3.0.x --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 36 ++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 80998b82a..4221c9db7 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -91,6 +91,8 @@ class EntityGenerator +use Doctrine\ORM\Mapping as ORM; + { @@ -146,11 +148,18 @@ public function () } '; + public function __construct() + { + if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) { + $this->_annotationsPrefix = 'ORM\\'; + } + } + /** * Generate and write entity classes for the given array of ClassMetadataInfo instances * * @param array $metadatas - * @param string $outputDirectory + * @param string $outputDirectory * @return void */ public function generate(array $metadatas, $outputDirectory) @@ -164,7 +173,7 @@ public function () * Generated and write entity class to disk for the given ClassMetadataInfo instance * * @param ClassMetadataInfo $metadata - * @param string $outputDirectory + * @param string $outputDirectory * @return void */ public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) @@ -201,7 +210,7 @@ public function () /** * Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance * - * @param ClassMetadataInfo $metadata + * @param ClassMetadataInfo $metadata * @return string $code */ public function generateEntityClass(ClassMetadataInfo $metadata) @@ -227,8 +236,8 @@ public function () /** * Generate the updated code for the given ClassMetadataInfo and entity at path * - * @param ClassMetadataInfo $metadata - * @param string $path + * @param ClassMetadataInfo $metadata + * @param string $path * @return string $code; */ public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path) @@ -245,7 +254,7 @@ public function () /** * Set the number of spaces the exported class should have * - * @param integer $numSpaces + * @param integer $numSpaces * @return void */ public function setNumSpaces($numSpaces) @@ -257,7 +266,7 @@ public function () /** * Set the extension to use when writing php files to disk * - * @param string $extension + * @param string $extension * @return void */ public function setExtension($extension) @@ -278,7 +287,7 @@ public function () /** * Set whether or not to generate annotations for the entity * - * @param bool $bool + * @param bool $bool * @return void */ public function setGenerateAnnotations($bool) @@ -293,13 +302,16 @@ public function () */ public function setAnnotationPrefix($prefix) { + if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) { + return; + } $this->_annotationsPrefix = $prefix; } /** * Set whether or not to try and update the entity if it already exists * - * @param bool $bool + * @param bool $bool * @return void */ public function setUpdateEntityIfExists($bool) @@ -407,7 +419,7 @@ public function () $tokens = token_get_all($src); $lastSeenNamespace = ""; $lastSeenClass = false; - + $inNamespace = false; $inClass = false; for ($i = 0; $i < count($tokens); $i++) { @@ -808,7 +820,7 @@ public function () if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"'; if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"'; - $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}'; + $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}'; } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) { @@ -862,7 +874,7 @@ public function () $lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'OrderBy({'; foreach ($associationMapping['orderBy'] as $name => $direction) { - $lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",'; + $lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",'; } $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1); From 7ee8dc4e44e3a761b8dab23a18ded74c9f10d7a3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 2 Jun 2011 21:45:03 +0200 Subject: [PATCH 04/10] DDC-1179 - Make it possible to specify a namespace when mapping with --from-database --- .../ORM/Mapping/Driver/DatabaseDriver.php | 22 +++++++++++++++++-- .../Console/Command/ConvertMappingCommand.php | 18 +++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php index 2805d5a21..0ee1060c7 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php @@ -66,6 +66,13 @@ class DatabaseDriver implements Driver */ private $fieldNamesForColumns = array(); + /** + * The namespace for the generated entities. + * + * @var string + */ + private $namespace; + /** * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading * docblock annotations. @@ -348,10 +355,10 @@ class DatabaseDriver implements Driver private function getClassNameForTable($tableName) { if (isset($this->classNamesForTables[$tableName])) { - return $this->classNamesForTables[$tableName]; + return $this->namespace . $this->classNamesForTables[$tableName]; } - return Inflector::classify(strtolower($tableName)); + return $this->namespace . Inflector::classify(strtolower($tableName)); } /** @@ -376,4 +383,15 @@ class DatabaseDriver implements Driver } return Inflector::camelize($columnName); } + + /** + * Set the namespace for the generated entities. + * + * @param string $namespace + * @return void + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + } } diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php index 8c1a8fea1..d694999e4 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php @@ -78,6 +78,10 @@ class ConvertMappingCommand extends Console\Command\Command 'num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4 ), + new InputOption( + 'namespace', null, InputOption::VALUE_OPTIONAL, + 'Defines a namespace for the generated entity classes, if converted from database.' + ), )) ->setHelp(<<getHelper('em')->getEntityManager(); if ($input->getOption('from-database') === true) { - $em->getConfiguration()->setMetadataDriverImpl( - new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( - $em->getConnection()->getSchemaManager() - ) + $databaseDriver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( + $em->getConnection()->getSchemaManager() ); + + $em->getConfiguration()->setMetadataDriverImpl( + $databaseDriver + ); + + if (($namespace = $input->getOption('namespace')) !== null) { + $databaseDriver->setNamespace($namespace); + } } $cmf = new DisconnectedClassMetadataFactory(); From bf0775fbb6ab3e0a2f8288f3dff81228badba0f1 Mon Sep 17 00:00:00 2001 From: Miha Vrhovnik Date: Fri, 3 Jun 2011 09:12:32 +0200 Subject: [PATCH 05/10] Add extension points into the xml schema --- doctrine-mapping.xsd | 94 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index 6216d75dc..126f30290 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -17,11 +17,18 @@ + + - + + + + + + @@ -30,7 +37,9 @@ + + @@ -46,24 +55,32 @@ + + + + + + + + @@ -81,6 +98,7 @@ + @@ -89,11 +107,17 @@ + - + + + + + + @@ -139,6 +163,9 @@ + + + @@ -149,76 +176,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -226,32 +291,43 @@ + + + + + + + - - + + + + + + @@ -266,6 +342,7 @@ + @@ -273,12 +350,14 @@ + + @@ -286,6 +365,7 @@ + @@ -294,13 +374,16 @@ + + + @@ -309,7 +392,9 @@ + + @@ -317,6 +402,7 @@ + From 79643e32ed3f3a6679428b5d6dc152e70fed5c13 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 3 Jun 2011 07:58:49 -0500 Subject: [PATCH 06/10] [Tools][Console] Refactoring the UpdateCommand There are two basic changes: 1) Changed --force and --dump-sql from options to a single argument. Prior, you couldn't pass both options simultaneously anyways, so making them an argument is more accurate. 2) Changed the language and formatting of the task to be more user-friendly. --- .../Command/SchemaTool/UpdateCommand.php | 88 ++++++++++++------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php index 195f5efef..a163b6f84 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php @@ -28,7 +28,8 @@ use Symfony\Component\Console\Input\InputArgument, Doctrine\ORM\Tools\SchemaTool; /** - * Command to update the database schema for a set of classes based on their mappings. + * Command to generate the SQL needed to update the database schema to match + * the current mapping information. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org @@ -38,6 +39,7 @@ use Symfony\Component\Console\Input\InputArgument, * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel + * @author Ryan Weaver */ class UpdateCommand extends AbstractCommand { @@ -49,26 +51,39 @@ class UpdateCommand extends AbstractCommand $this ->setName('orm:schema-tool:update') ->setDescription( - 'Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.' + 'Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.' ) ->setDefinition(array( + new InputArgument( + 'action', InputArgument::OPTIONAL, + 'Either "execute" (execute the SQL) or "dump-sql" (dump the SQL to the screen). If not specified, nothing is done.' + ), new InputOption( 'complete', null, InputOption::VALUE_NONE, 'If defined, all assets of the database which are not relevant to the current metadata will be dropped.' ), - new InputOption( - 'dump-sql', null, InputOption::VALUE_NONE, - 'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.' - ), - new InputOption( - 'force', null, InputOption::VALUE_NONE, - "Don't ask for the incremental update of the database, but force the operation to run." - ), - )) - ->setHelp(<<getFullName(); + $this->setHelp(<<$fullName command generates the SQL needed to +synchronize the database schema with the current mapping metadata of the +default entity manager. + +For example, if you add metadata for a new column to an entity, this command +would generate and output the SQL needed to add the new column to the database: + +$fullName dump-sql + +Alternatively, you can execute the generated queries: + +$fullName execute + +Finally, be aware that if the --complete option is passed, this +task will drop all database assets (e.g. tables, etc) that are *not* described +by the current metadata. In other words, without this option, this task leaves +untouched any "extra" tables that exist in the database, but which aren't +described by any metadata. EOT ); } @@ -78,26 +93,33 @@ EOT // Defining if update is complete or not (--complete not defined means $saveMode = true) $saveMode = ($input->getOption('complete') !== true); - if ($input->getOption('dump-sql') === true) { - $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode); - $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL); - } else if ($input->getOption('force') === true) { - $output->write('Updating database schema...' . PHP_EOL); + $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode); + if (0 == count($sqls)) { + $output->writeln('Nothing to update - your database is already in sync with the current entity metadata.'); + + return; + } + + $action = $input->getArgument('action'); + if ('execute' == $action) { + $output->writeln('Updating database schema...'); $schemaTool->updateSchema($metadatas, $saveMode); - $output->write('Database schema updated successfully!' . PHP_EOL); + $output->writeln(sprintf('Database schema updated successfully! "%s" queries were executed', count($sqls))); + } else if ('dump-sql' == $action) { + $output->writeln(implode(';' . PHP_EOL, $sqls)); + } else if (null === $action) { + $output->writeln('ATTENTION: This operation should not be executed in a production environment.'); + $output->writeln(' Use the incremental update to detect changes during development and use'); + $output->writeln(' the SQL DDL provided to manually update your database in production.'); + $output->writeln(''); + + $output->writeln(sprintf('The Schema-Tool would execute "%s" queries to update the database.', count($sqls))); + $output->writeln('Please run the operation by passing an argument to this command:'); + + $output->writeln(sprintf(' %s execute to execute the command', $this->getFullName())); + $output->writeln(sprintf(' %s dump-sql to dump the SQL statements to the screen', $this->getFullName())); } else { - $output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL); - $output->write('Use the incremental update to detect changes during development and use' . PHP_EOL); - $output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL); - - $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode); - - if (count($sqls)) { - $output->write('Schema-Tool would execute ' . count($sqls) . ' queries to update the database.' . PHP_EOL); - $output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL); - } else { - $output->write('Nothing to update. The database is in sync with the current entity metadata.' . PHP_EOL); - } + throw new \InvalidArgumentException(sprintf('The first argument - if specified - should be either "execute" or "dump-sql" ("%s" given).', $action)); } } } From 64687409155f5e547b2611db1eed4948fba847f9 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 3 Jun 2011 15:09:18 -0500 Subject: [PATCH 07/10] [Tools][Console] Reworking changes to be more backwards compatible This keeps the --dump-sql and --force options, but adds an exception if you try to use them both (which previously, only dumped the SQL but didn't tell you that it was *not* in fact also executing the queries). One additional change is the introduction of a `$name` property, which was the only way that a parent task could allow a child task to override the task's name early enough that the task's overridden name is taken to account when the parent class references it for its help message. --- .../Command/SchemaTool/UpdateCommand.php | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php index a163b6f84..ea43f770b 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php @@ -43,25 +43,32 @@ use Symfony\Component\Console\Input\InputArgument, */ class UpdateCommand extends AbstractCommand { + protected $name = 'orm:schema-tool:update'; + /** * @see Console\Command\Command */ protected function configure() { $this - ->setName('orm:schema-tool:update') + ->setName($this->name) ->setDescription( 'Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.' ) ->setDefinition(array( - new InputArgument( - 'action', InputArgument::OPTIONAL, - 'Either "execute" (execute the SQL) or "dump-sql" (dump the SQL to the screen). If not specified, nothing is done.' - ), new InputOption( 'complete', null, InputOption::VALUE_NONE, 'If defined, all assets of the database which are not relevant to the current metadata will be dropped.' ), + + new InputOption( + 'dump-sql', null, InputOption::VALUE_NONE, + 'Dumps the generated SQL statements to the screen (does not execute them).' + ), + new InputOption( + 'force', null, InputOption::VALUE_NONE, + 'Causes the generated SQL statements to be physically executed against your database.' + ), )); $fullName = $this->getFullName(); @@ -73,11 +80,11 @@ default entity manager. For example, if you add metadata for a new column to an entity, this command would generate and output the SQL needed to add the new column to the database: -$fullName dump-sql +$fullName --dump-sql Alternatively, you can execute the generated queries: -$fullName execute +$fullName --force Finally, be aware that if the --complete option is passed, this task will drop all database assets (e.g. tables, etc) that are *not* described @@ -100,26 +107,29 @@ EOT return; } - $action = $input->getArgument('action'); - if ('execute' == $action) { + $dumpSql = (true === $input->getOption('dump-sql')); + $force = (true === $input->getOption('force')); + if ($dumpSql && $force) { + throw new \InvalidArgumentException('You can pass either the --dump-sql or the --force option (but not both simultaneously).'); + } + + if ($dumpSql) { + $output->writeln(implode(';' . PHP_EOL, $sqls)); + } else if ($force) { $output->writeln('Updating database schema...'); $schemaTool->updateSchema($metadatas, $saveMode); $output->writeln(sprintf('Database schema updated successfully! "%s" queries were executed', count($sqls))); - } else if ('dump-sql' == $action) { - $output->writeln(implode(';' . PHP_EOL, $sqls)); - } else if (null === $action) { + } else { $output->writeln('ATTENTION: This operation should not be executed in a production environment.'); $output->writeln(' Use the incremental update to detect changes during development and use'); $output->writeln(' the SQL DDL provided to manually update your database in production.'); $output->writeln(''); $output->writeln(sprintf('The Schema-Tool would execute "%s" queries to update the database.', count($sqls))); - $output->writeln('Please run the operation by passing an argument to this command:'); + $output->writeln('Please run the operation by passing one of the following options:'); - $output->writeln(sprintf(' %s execute to execute the command', $this->getFullName())); - $output->writeln(sprintf(' %s dump-sql to dump the SQL statements to the screen', $this->getFullName())); - } else { - throw new \InvalidArgumentException(sprintf('The first argument - if specified - should be either "execute" or "dump-sql" ("%s" given).', $action)); + $output->writeln(sprintf(' %s --force to execute the command', $this->getFullName())); + $output->writeln(sprintf(' %s --dump-sql to dump the SQL statements to the screen', $this->getFullName())); } } } From 3cdb4e007d6e15c011bb00ced8606d1c9303ee29 Mon Sep 17 00:00:00 2001 From: NicoB Date: Tue, 7 Jun 2011 18:55:52 +0700 Subject: [PATCH 08/10] joinTable can be undefined because ManyToMAny generation is bidirectional with inverse sides --- lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php index d5c1efd8a..669e76ce4 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php @@ -181,7 +181,7 @@ class YamlExporter extends AbstractExporter $manyToManyMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], - 'joinTable' => $associationMapping['joinTable'], + 'joinTable' => isset($associationMapping['joinTable']) ? $associationMapping['joinTable'] : null, 'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null ); From 1f6b49d2364576d36c515c785b9c7a0e4b8c3bc8 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 9 Jun 2011 15:42:40 -0300 Subject: [PATCH 09/10] Added getRootEntities to QueryBuilder. --- lib/Doctrine/ORM/QueryBuilder.php | 37 ++++++++++++++++++- tests/Doctrine/Tests/ORM/QueryBuilderTest.php | 9 +++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 10eaebbe2..b67a550d8 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -240,7 +240,7 @@ class QueryBuilder } /** - * Gets the root alias of the query. This is the first entity alias involved + * Gets the root aliases of the query. This is the entity aliases involved * in the construction of the query. * * @@ -251,7 +251,7 @@ class QueryBuilder * $qb->getRootAliases(); // array('u') * * - * @return string $rootAlias + * @return array $rootAliases */ public function getRootAliases() { @@ -272,6 +272,39 @@ class QueryBuilder return $aliases; } + /** + * Gets the root entities of the query. This is the entity aliases involved + * in the construction of the query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u'); + * + * $qb->getRootEntities(); // array('User') + * + * + * @return array $rootEntities + */ + public function getRootEntities() + { + $entities = array(); + + foreach ($this->_dqlParts['from'] as &$fromClause) { + if (is_string($fromClause)) { + $spacePos = strrpos($fromClause, ' '); + $from = substr($fromClause, 0, $spacePos); + $alias = substr($fromClause, $spacePos + 1); + + $fromClause = new Query\Expr\From($from, $alias); + } + + $entities[] = $fromClause->getFrom(); + } + + return $entities; + } + /** * Sets a query parameter for the query being constructed. * diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index cf570f52c..2cde45223 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -652,6 +652,15 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(array('u'), $qb->getRootAliases()); } + public function testGetRootEntities() + { + $qb = $this->_em->createQueryBuilder() + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + + $this->assertEquals(array('Doctrine\Tests\Models\CMS\CmsUser'), $qb->getRootEntities()); + } + public function testGetSeveralRootAliases() { $qb = $this->_em->createQueryBuilder() From c7eaf77d156ae035ab51ea006891ec19f065c5dd Mon Sep 17 00:00:00 2001 From: Bertrand Zuchuat Date: Sun, 12 Jun 2011 14:46:02 +0200 Subject: [PATCH 10/10] Renamed function getFullName with getName to match with last change on Symfony Console --- .../ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php index ea43f770b..b96a5a673 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php @@ -71,7 +71,7 @@ class UpdateCommand extends AbstractCommand ), )); - $fullName = $this->getFullName(); + $fullName = $this->getName(); $this->setHelp(<<$fullName command generates the SQL needed to synchronize the database schema with the current mapping metadata of the