. */ #namespace Doctrine::DBAL::Export; /** * Doctrine_Export * * @package Doctrine * @subpackage Export * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 * @version $Revision$ * @todo Rename to ExportManager. Subclasses: MySqlExportManager, PgSqlExportManager etc. */ class Doctrine_Export extends Doctrine_Connection_Module { /** * exportSchema * method for exporting Doctrine_Entity classes to a schema * * if the directory parameter is given this method first iterates * recursively trhough the given directory in order to find any model classes * * Then it iterates through all declared classes and creates tables for the ones * that extend Doctrine_Entity and are not abstract classes * * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS * occurred during the create table operation * @param string $directory optional directory parameter * @return void */ public function exportSchema($directory = null) { if ($directory !== null) { $models = Doctrine::loadModels($directory); } else { $models = Doctrine::getLoadedModels(); } $this->exportClasses($models); } /** * exportClasses * * FIXME: This method is a big huge hack. The sql needs to be executed in the correct order. I have some stupid logic to * make sure they are in the right order. * * method for exporting Doctrine_Entity classes to a schema * * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS * occurred during the create table operation * @param array $classes * @return void * @todo ORM stuff */ public function exportClasses(array $classes) { $connections = array(); foreach ($classes as $class) { $record = new $class(); $connection = $record->getTable()->getConnection(); $connectionName = Doctrine_Manager::getInstance()->getConnectionName($connection); if ( ! isset($connections[$connectionName])) { $connections[$connectionName] = array( 'create_tables' => array(), 'create_sequences' => array(), 'alters' => array() ); } $sql = $this->exportClassesSql(array($class)); // Build array of all the creates // We need these to happen first foreach ($sql as $key => $query) { if (strstr($query, 'CREATE TABLE')) { $connections[$connectionName]['create_tables'][] = $query; unset($sql[$key]); } if (strstr($query, 'CREATE SEQUENCE')) { $connections[$connectionName]['create_sequences'][] = $query; unset($sql[$key]); } } $connections[$connectionName]['alters'] = array_merge($connections[$connectionName]['alters'], $sql); } // Loop over all the sql again to merge the creates and alters in to the same array, but so that the alters are at the bottom $build = array(); foreach ($connections as $connectionName => $sql) { $build[$connectionName] = array_unique(array_merge($sql['create_tables'], $sql['create_sequences'], $sql['alters'])); } foreach ($build as $connectionName => $sql) { $connection = Doctrine_Manager::getInstance()->getConnection($connectionName); $connection->beginTransaction(); foreach ($sql as $query) { try { $connection->exec($query); } catch (Doctrine_Connection_Exception $e) { // we only want to silence table already exists errors if ($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { $connection->rollback(); throw new Doctrine_Export_Exception($e->getMessage() . '. Failing Query: ' . $query); } } } $connection->commit(); } } /** * exportClassesSql * method for exporting Doctrine_Entity classes to a schema * * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS * occurred during the create table operation * @param array $classes * @return void * @todo package:orm */ public function exportClassesSql(array $classes) { $models = Doctrine::filterInvalidModels($classes); $sql = array(); $finishedClasses = array(); foreach ($models as $name) { if (in_array($name, $finishedClasses)) { continue; } $classMetadata = $this->conn->getClassMetadata($name); // In Class Table Inheritance we have to make sure that ALL tables of parent classes // are exported, too as soon as ONE table is exported, because the data of one class is stored // across many tables. if ($classMetadata->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED) { $parents = $classMetadata->getParentClasses(); foreach ($parents as $parent) { $data = $classMetadata->getConnection()->getClassMetadata($parent)->getExportableFormat(); $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']); $sql = array_merge($sql, (array) $query); $finishedClasses[] = $parent; } } $data = $classMetadata->getExportableFormat(); $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']); if (is_array($query)) { $sql = array_merge($sql, $query); } else { $sql[] = $query; } if ($classMetadata->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_PLUGINS) { $sql = array_merge($sql, $this->exportGeneratorsSql($classMetadata)); } } $sql = array_unique($sql); rsort($sql); return $sql; } /** * fetches all generators recursively for given table * * @param Doctrine_Table $table table object to retrieve the generators from * @return array an array of Doctrine_Record_Generator objects * @todo package:orm */ public function getAllGenerators(Doctrine_ClassMetadata $table) { $generators = array(); foreach ($table->getGenerators() as $name => $generator) { if ($generator === null) { continue; } $generators[] = $generator; $generatorTable = $generator->getTable(); if ($generatorTable instanceof Doctrine_Table) { $generators = array_merge($generators, $this->getAllGenerators($generatorTable)); } } return $generators; } /** * exportGeneratorsSql * exports plugin tables for given table * * @param Doctrine_Table $table the table in which the generators belong to * @return array an array of sql strings * @todo package:orm */ public function exportGeneratorsSql(Doctrine_ClassMetadata $class) { $sql = array(); foreach ($this->getAllGenerators($class) as $name => $generator) { $table = $generator->getTable(); // Make sure plugin has a valid table if ($table instanceof Doctrine_Table) { $data = $table->getExportableFormat(); $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']); $sql = array_merge($sql, (array) $query); } } return $sql; } /** * exportSql * returns the sql for exporting Doctrine_Entity classes to a schema * * if the directory parameter is given this method first iterates * recursively trhough the given directory in order to find any model classes * * Then it iterates through all declared classes and creates tables for the ones * that extend Doctrine_Entity and are not abstract classes * * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS * occurred during the create table operation * @param string $directory optional directory parameter * @return void */ public function exportSql($directory = null) { if ($directory !== null) { $models = Doctrine::loadModels($directory); } else { $models = Doctrine::getLoadedModels(); } return $this->exportClassesSql($models); } /** * exportTable * exports given table into database based on column and option definitions * * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS * occurred during the create table operation * @return boolean whether or not the export operation was successful * false if table already existed in the database * @todo ORM stuff */ public function exportTable(Doctrine_ClassMetadata $metadata) { /** TODO: maybe there should be portability option for the following check if ( ! Doctrine::isValidClassname($table->getOption('declaringClass')->getName())) { throw new Doctrine_Export_Exception('Class name not valid.'); } */ try { $data = $metadata->getExportableFormat(); $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']); } catch (Doctrine_Connection_Exception $e) { // we only want to silence table already exists errors if ($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { throw $e; } } } }