Permalink
Browse files

Add schema builder task

This task allows the generation of the schema for a single table in a specified database.
  • Loading branch information...
Henning Glatter-Gotz
Henning Glatter-Gotz committed Jan 27, 2012
1 parent 297c931 commit 9fd80b926a494c6b71c4424027a4dd1a9d6c0d22
Showing with 241 additions and 0 deletions.
  1. +196 −0 lib/SchemaBuilder/SchemaBuilder.class.php
  2. +45 −0 lib/task/doctrine/sfDoctrineBuildTableSchemaTask.class.php
@@ -0,0 +1,196 @@
+<?php
+/**
+ * @package uUtilitiesPlugin
+ * @subpackage SchemaBuilder
+ * @author Henning Glatter-Gotz <henning@glatter-gotz.com>
+ */
+class SchemaBuilder
+{
+ public function __construct()
+ {}
+
+ public static function getInstance()
+ {
+ return new self();
+ }
+
+ /**
+ * Update the existing schema.yml file with any new tables that were passed
+ * to the method.
+ *
+ * @param string $outputFile The full path of the current schema file
+ * @param array $connections An associative array of connections and their
+ * tables. See buildPHPModels
+ * @param array $options Any options to be passed to
+ * Doctrine_Import_Builder
+ * @return type
+ */
+ public function update($outputFile, array $connections = array(), array $options = array())
+ {
+ try
+ {
+ $directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'tmp_doctrine_models';
+
+ $options['generateBaseClasses'] = isset($options['generateBaseClasses']) ? $options['generateBaseClasses'] : false;
+ $models = $this->buildPHPModels($directory, $connections, $options);
+
+ if (empty($models) && !is_dir($directory))
+ {
+ throw new Exception('No models generated from your databases');
+ }
+
+ $this->generateYaml($outputFile, $directory, array(), Doctrine_Core::MODEL_LOADING_AGGRESSIVE);
+
+ Doctrine_Lib::removeDirectories($directory);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception(__METHOD__ . ':' . __LINE__ . '|' . $e->getMessage());
+ }
+
+ return 0;
+ }
+
+ /**
+ * Build the schema for multiple connections and specific tables for those
+ * connections.
+ *
+ * Example:
+ * $connections = array(
+ * 'connection1' => array('table1', 'table2'),
+ * 'connection2' => array('table1', 'table2')
+ * );
+ *
+ * @param type $directory
+ * @param array $connections Array of connection names with their associated
+ * tables
+ * @param array $options
+ * @return array
+ */
+ protected function buildPHPModels($directory, array $connections = array(), array $options = array())
+ {
+ $classes = array();
+
+ $manager = Doctrine_Manager::getInstance();
+
+ foreach ($manager as $name => $connection)
+ {
+ // Limit the databases to the ones specified by $connections.
+ // Check only happens if array is not empty
+ $connectionNames = array_keys($connections);
+
+ if (!empty($connections) && !in_array($name, $connectionNames))
+ {
+ continue;
+ }
+
+ $builder = new Doctrine_Import_Builder();
+ $builder->setTargetPath($directory);
+ $builder->setOptions($options);
+
+ $definitions = array();
+ $currentConnName = $connection->getName();
+
+ foreach ($connection->import->listTables() as $table)
+ {
+ if (!in_array($table, $connections[$currentConnName]))
+ {
+ continue;
+ }
+
+ $definition = array();
+ $definition['tableName'] = $table;
+ $definition['className'] = Doctrine_Inflector::classify(Doctrine_Inflector::tableize($table));
+ $definition['columns'] = $connection->import->listTableColumns($table);
+ $definition['connection'] = $connection->getName();
+ $definition['connectionClassName'] = $definition['className'];
+
+ try
+ {
+ $definition['relations'] = array();
+ $relations = $connection->import->listTableRelations($table);
+ $relClasses = array();
+ foreach ($relations as $relation)
+ {
+ $table = $relation['table'];
+ $class = Doctrine_Inflector::classify(Doctrine_Inflector::tableize($table));
+ if (in_array($class, $relClasses))
+ {
+ $alias = $class . '_' . (count($relClasses) + 1);
+ }
+ else
+ {
+ $alias = $class;
+ }
+ $relClasses[] = $class;
+ $definition['relations'][$alias] = array(
+ 'alias' => $alias,
+ 'class' => $class,
+ 'local' => $relation['local'],
+ 'foreign' => $relation['foreign']
+ );
+ }
+ }
+ catch (Exception $e)
+ {
+
+ }
+
+ $definitions[strtolower($definition['className'])] = $definition;
+ $classes[] = $definition['className'];
+ }
+
+ // Build opposite end of relationships
+ foreach ($definitions as $definition)
+ {
+ $className = $definition['className'];
+ $relClasses = array();
+ foreach ($definition['relations'] as $alias => $relation)
+ {
+ if (in_array($relation['class'], $relClasses) || isset($definitions[$relation['class']]['relations'][$className]))
+ {
+ $alias = $className . '_' . (count($relClasses) + 1);
+ }
+ else
+ {
+ $alias = $className;
+ }
+ $relClasses[] = $relation['class'];
+ $definitions[strtolower($relation['class'])]['relations'][$alias] = array(
+ 'type' => Doctrine_Relation::MANY,
+ 'alias' => $alias,
+ 'class' => $className,
+ 'local' => $relation['foreign'],
+ 'foreign' => $relation['local']
+ );
+ }
+ }
+
+ // Build records
+ foreach ($definitions as $definition)
+ {
+ $builder->buildRecord($definition);
+ }
+ }
+
+ return $classes;
+ }
+
+ protected function generateYaml($schemaPath, $directory = null, $models = array(), $modelLoading = null)
+ {
+ $currentProjectModels = (array) sfYaml::load($schemaPath);
+
+ $export = new Doctrine_Export_Schema();
+ $newProjectModels = $export->buildSchema($directory, $models, $modelLoading);
+
+ $projectModels = array_merge($currentProjectModels, $newProjectModels);
+
+ if (is_dir($schemaPath))
+ {
+ $schemaPath = $schemaPath . DIRECTORY_SEPARATOR . 'schema.yml';
+ }
+
+ return Doctrine_Parser::dump($projectModels, 'yml', $schemaPath);
+ }
+
+}
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @package uUtilitiesPlugin
+ * @subpackage SchemaBuilder
+ * @author Henning Glatter-Gotz <henning@glatter-gotz.com>
+ */
+class sfDoctrineBuildTableSchemaTask extends sfDoctrineBaseTask
+{
+ protected function configure()
+ {
+ $this->addOptions(array(
+ new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name'),
+ new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'prod'),
+ new sfCommandOption('connection', null, sfCommandOption::PARAMETER_REQUIRED, 'The connection name', 'doctrine'),
+ new sfCommandOption('table', null, sfCommandOption::PARAMETER_REQUIRED, 'The name of the database table'),
+ ));
+
+ $this->namespace = 'doctrine';
+ $this->name = 'build-table-schema';
+ $this->briefDescription = 'Generate the schema for a specific table in a database.';
+ $this->detailedDescription = <<<EOF
+The [build-table-schema|INFO] task adds the schema for a single table to the
+main schema.yml file. This can be very useful when dealing with a large number
+of databases in a project and not all tables are managed with Doctrine Models.
+
+Call it with:
+
+ [php symfony build-table-schema|INFO]
+EOF;
+ }
+
+ protected function execute($arguments = array(), $options = array())
+ {
+ $databaseManager = new sfDatabaseManager($this->configuration);
+
+ $this->logSection('doctrine', 'generating yaml schema from database');
+
+ $config = $this->getCliConfig();
+ $schemaPath = $config['yaml_schema_path'].uFs::DS.'schema.yml';
+ $connections = array($options['connection'] => array($options['table']));
+ $builderOptions = array();
+
+ return SchemaBuilder::getInstance()->update($schemaPath, $connections, $builderOptions);
+ }
+}

0 comments on commit 9fd80b9

Please sign in to comment.