SchemaTool.php 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\ORM\Tools;

24
use Doctrine\DBAL\Types\Type;
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
use Doctrine\ORM\EntityManager;

/**
 * The SchemaTool is a tool to create and/or drop database schemas based on
 * <tt>ClassMetadata</tt> class descriptors.
 *
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @author      Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
 * @author      Roman Borschel <roman@code-factory.org>
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.org
 * @since       2.0
 * @version     $Revision: 4805 $
 */
class SchemaTool
{
    /** The EntityManager */
    private $_em;
    /** The DatabasePlatform */
    private $_platform;

    /**
     * Initializes a new SchemaTool instance that uses the connection of the
     * provided EntityManager.
     *
     * @param Doctrine\ORM\EntityManager $em
     */
    public function __construct(EntityManager $em)
    {
        $this->_em = $em;
        $this->_platform = $em->getConnection()->getDatabasePlatform();
    }

    /**
     * Creates the database schema for the given array of ClassMetadata instances.
     *
     * @param array $classes
     */
    public function createSchema(array $classes)
    {
        $createSchemaSql = $this->getCreateSchemaSql($classes);
        $conn = $this->_em->getConnection();
        foreach ($createSchemaSql as $sql) {
            $conn->execute($sql);
        }
    }

    /**
     * Gets an array of DDL statements for the specified array of ClassMetadata instances.
     *
     * @param array $classes
     * @return array $sql
     */
    public function getCreateSchemaSql(array $classes)
    {
80 81 82 83
        $sql = array(); // All SQL statements
        $processedClasses = array(); // Reminder for processed classes, used for hierarchies
        $foreignKeyConstraints = array(); // FK SQL statements. Appended to $sql at the end.
        $sequences = array(); // Sequence SQL statements. Appended to $sql at the end.
84 85

        foreach ($classes as $class) {
86
            if (isset($processedClasses[$class->name])) {
87
                continue;
88 89
            }

90
            $options = array(); // table options
91
            $columns = array(); // table columns
92
            
93
            if ($class->isInheritanceTypeSingleTable()) {
94 95 96
                $columns = $this->_gatherColumns($class, $options);
                $this->_gatherRelationsSql($class, $sql, $columns, $foreignKeyConstraints);
                
97 98 99 100 101
                // Add the discriminator column
                $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class);
                $columns[$discrColumnDef['name']] = $discrColumnDef;

                // Aggregate all the information from all classes in the hierarchy
102
                foreach ($class->parentClasses as $parentClassName) {
103 104 105
                    // Parent class information is already contained in this class
                    $processedClasses[$parentClassName] = true;
                }
106
                foreach ($class->subClasses as $subClassName) {
107 108 109
                    $subClass = $this->_em->getClassMetadata($subClassName);
                    $columns = array_merge($columns, $this->_gatherColumns($subClass, $options));
                    $this->_gatherRelationsSql($subClass, $sql, $columns, $foreignKeyConstraints);
110 111 112
                    $processedClasses[$subClassName] = true;
                }
            } else if ($class->isInheritanceTypeJoined()) {
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
                // Add all non-inherited fields as columns
                foreach ($class->fieldMappings as $fieldName => $mapping) {
                    if ( ! isset($mapping['inherited'])) {
                        $columns[$mapping['columnName']] = $this->_gatherColumn($class, $mapping, $options);
                    }
                }

                $this->_gatherRelationsSql($class, $sql, $columns, $foreignKeyConstraints);

                // Add the discriminator column only to the root table
                if ($class->name == $class->rootEntityName) {
                    $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class);
                    $columns[$discrColumnDef['name']] = $discrColumnDef;
                } else {
                    // Add an ID FK column to child tables
                    $idMapping = $class->fieldMappings[$class->identifier[0]];
                    $idColumn = $this->_gatherColumn($class, $idMapping, $options);
                    unset($idColumn['autoincrement']);
                    $columns[$idMapping['columnName']] = $idColumn;
                    // Add a FK constraint on the ID column
                    $constraint = array();
                    $constraint['tableName'] = $class->getTableName();
                    $constraint['foreignTable'] = $this->_em->getClassMetadata($class->rootEntityName)->getTableName();
                    $constraint['local'] = array($idMapping['columnName']);
                    $constraint['foreign'] = array($idMapping['columnName']);
                    $constraint['onDelete'] = 'CASCADE';
                    $foreignKeyConstraints[] = $constraint;
                }

142 143
            } else if ($class->isInheritanceTypeTablePerClass()) {
                //TODO
144 145 146
            } else {
                $columns = $this->_gatherColumns($class, $options);
                $this->_gatherRelationsSql($class, $sql, $columns, $foreignKeyConstraints);
147 148
            }

149
            $sql = array_merge($sql, $this->_platform->getCreateTableSql($class->getTableName(), $columns, $options));
150
            $processedClasses[$class->name] = true;
151

152
            if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) {
153 154 155 156 157 158 159
                $seqDef = $class->getSequenceGeneratorDefinition();
                $sequences[] = $this->_platform->getCreateSequenceSql(
                    $seqDef['sequenceName'],
                    $seqDef['initialValue'],
                    $seqDef['allocationSize']
                );
            }
160 161
        }

162
        // Append the foreign key constraints SQL
163 164 165 166 167 168
        if ($this->_platform->supportsForeignKeyConstraints()) {
            foreach ($foreignKeyConstraints as $fkConstraint) {
                $sql = array_merge($sql, (array)$this->_platform->getCreateForeignKeySql($fkConstraint['tableName'], $fkConstraint));
            }
        }

169 170 171
        // Append the sequence SQL
        $sql = array_merge($sql, $sequences);

172 173 174
        return $sql;
    }

175 176
    private function _getDiscriminatorColumnDefinition($class)
    {
177
        $discrColumn = $class->discriminatorColumn;
178 179 180 181 182 183 184 185
        return array(
            'name' => $discrColumn['name'],
            'type' => Type::getType($discrColumn['type']),
            'length' => $discrColumn['length'],
            'notnull' => true
        );
    }

186 187 188 189 190 191 192
    /**
     * Gathers the column definitions of all field mappings found in the given class.
     *
     * @param ClassMetadata $class
     * @param array $options
     * @return array
     */
193
    private function _gatherColumns($class, array &$options)
194 195
    {
        $columns = array();
196
        foreach ($class->fieldMappings as $fieldName => $mapping) {
197
            $columns[$mapping['columnName']] = $this->_gatherColumn($class, $mapping, $options);
198
        }
199
        
200 201 202
        return $columns;
    }

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    private function _gatherColumn($class, array $mapping, array &$options)
    {
        $column = array();
        $column['name'] = $mapping['columnName'];
        $column['type'] = Type::getType($mapping['type']);
        $column['length'] = $mapping['length'];
        $column['notnull'] = ! $mapping['nullable'];
        if ($class->isIdentifier($mapping['fieldName'])) {
            $column['primary'] = true;
            $options['primary'][] = $mapping['columnName'];
            if ($class->isIdGeneratorIdentity()) {
                $column['autoincrement'] = true;
            }
        }

        return $column;
    }

221 222
    private function _gatherRelationsSql($class, array &$sql, array &$columns, array &$constraints)
    {
223 224 225 226
        foreach ($class->associationMappings as $fieldName => $mapping) {
            if (isset($class->inheritedAssociationFields[$fieldName])) {
                continue;
            }
romanb's avatar
romanb committed
227

228 229
            $foreignClass = $this->_em->getClassMetadata($mapping->targetEntityName);
            if ($mapping->isOneToOne() && $mapping->isOwningSide) {
230 231 232 233 234 235 236 237
                $constraint = array();
                $constraint['tableName'] = $class->getTableName();
                $constraint['foreignTable'] = $foreignClass->getTableName();
                $constraint['local'] = array();
                $constraint['foreign'] = array();
                foreach ($mapping->getJoinColumns() as $joinColumn) {
                    $column = array();
                    $column['name'] = $joinColumn['name'];
238
                    $column['type'] = Type::getType($foreignClass->getTypeOfColumn($joinColumn['referencedColumnName']));
239 240 241 242 243
                    $columns[$joinColumn['name']] = $column;
                    $constraint['local'][] = $joinColumn['name'];
                    $constraint['foreign'][] = $joinColumn['referencedColumnName'];
                }
                $constraints[] = $constraint;
244
            } else if ($mapping->isOneToMany() && $mapping->isOwningSide) {
245 246
                //... create join table, one-many through join table supported later
                throw DoctrineException::updateMe("Not yet implemented.");
247
            } else if ($mapping->isManyToMany() && $mapping->isOwningSide) {
248 249 250 251 252 253 254 255 256 257 258 259 260 261
                // create join table
                $joinTableColumns = array();
                $joinTableOptions = array();
                $joinTable = $mapping->getJoinTable();
                $constraint1 = array();
                $constraint1['tableName'] = $joinTable['name'];
                $constraint1['foreignTable'] = $class->getTableName();
                $constraint1['local'] = array();
                $constraint1['foreign'] = array();
                foreach ($joinTable['joinColumns'] as $joinColumn) {
                    $column = array();
                    $column['primary'] = true;
                    $joinTableOptions['primary'][] = $joinColumn['name'];
                    $column['name'] = $joinColumn['name'];
262
                    $column['type'] = Type::getType($class->getTypeOfColumn($joinColumn['referencedColumnName']));
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
                    $joinTableColumns[$joinColumn['name']] = $column;
                    $constraint1['local'][] = $joinColumn['name'];
                    $constraint1['foreign'][] = $joinColumn['referencedColumnName'];
                }
                $constraints[] = $constraint1;

                $constraint2 = array();
                $constraint2['tableName'] = $joinTable['name'];
                $constraint2['foreignTable'] = $foreignClass->getTableName();
                $constraint2['local'] = array();
                $constraint2['foreign'] = array();
                foreach ($joinTable['inverseJoinColumns'] as $inverseJoinColumn) {
                    $column = array();
                    $column['primary'] = true;
                    $joinTableOptions['primary'][] = $inverseJoinColumn['name'];
                    $column['name'] = $inverseJoinColumn['name'];
279 280
                    $column['type'] = Type::getType($this->_em->getClassMetadata($mapping->getTargetEntityName())
                            ->getTypeOfColumn($inverseJoinColumn['referencedColumnName']));
281 282 283 284 285 286 287 288 289 290 291 292
                    $joinTableColumns[$inverseJoinColumn['name']] = $column;
                    $constraint2['local'][] = $inverseJoinColumn['name'];
                    $constraint2['foreign'][] = $inverseJoinColumn['referencedColumnName'];
                }
                $constraints[] = $constraint2;

                $sql = array_merge($sql, $this->_platform->getCreateTableSql(
                        $joinTable['name'], $joinTableColumns, $joinTableOptions));
            }
        }
    }

293 294 295 296 297 298 299 300 301 302
    public function dropSchema(array $classes)
    {
        //TODO
    }

    public function getDropSchemaSql(array $classes)
    {
        //TODO
    }
}