henning glatter-götz

./symfony doctrine:build-schema for a Single Table

The built-in command line tasks in symfony 1.4 (yes, I am still stuck on 1.4) are very handy for quickly performing all sorts of project related tasks. Doctrines own command line tasks are exposed through the symfony CLI in the doctrine namespace. I use the doctrine:build-schema task, which generates a yml schema for an existing database, all the time. This allows one to very rapidly build models from and for an existing database.

But therein also lies a problem - at least sometimes. I am often working on projects with multiple large databases that consist of dozens or hundreds of tables. And most often I only need to have models for a very small subset of the total list of tables. I used to simply generate the yml schema with ./symfony docrine:build-schema and then manually remove the tables I did not need. Horrible!! Slow, error prone and mind numbing.

It would be great if there was a more granular version of this task. For example

Add the schema for table X on database Y to the existing schema file

Enter doctrine:build-table-schema. I wrapped the functionality in a class that is really nothing more than a slight variation of part of the Doctrine_Import class. More specifically its importSchema method. But instead of building the schema for all tables of the database it simply builds the ones that have been explicitly requested.

SchemaBuilder.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php
/**
 * 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;
}

The entire code including a symfony task to call it is available as a symfony 1.4 plugin on github.com. It is part of a utilities plugin that is available at http://github.com/hglattergotz/uUtilitiesPlugin.

The class can be found here uUtilitiesPlugin/lib/SchemaBuilder

To add the schema for table A in database connection Z you would execute this from the symfony project folder:

1
2
./symfony doctrine:build-schema-table --connection=Z --table=A --application=myapp
./symfony doctrine:build-model

Comments