SchemaDiff.php 5.44 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Schema;

5
use Doctrine\DBAL\Internal\DependencyOrderCalculator;
6
use Doctrine\DBAL\Platforms\AbstractPlatform;
7

8
use function array_merge;
9

10
/**
Benjamin Morel's avatar
Benjamin Morel committed
11
 * Schema Diff.
12 13 14
 */
class SchemaDiff
{
Sergei Morozov's avatar
Sergei Morozov committed
15
    /** @var Schema|null */
16 17
    public $fromSchema;

Marco Pivetta's avatar
Marco Pivetta committed
18 19 20 21 22
    /**
     * All added namespaces.
     *
     * @var string[]
     */
23
    public $newNamespaces = [];
Marco Pivetta's avatar
Marco Pivetta committed
24

25 26 27 28 29
    /**
     * All removed namespaces.
     *
     * @var string[]
     */
30
    public $removedNamespaces = [];
31

32
    /**
Benjamin Morel's avatar
Benjamin Morel committed
33
     * All added tables.
34
     *
35
     * @var Table[]
36
     */
37
    public $newTables = [];
38 39

    /**
Benjamin Morel's avatar
Benjamin Morel committed
40
     * All changed tables.
41
     *
42
     * @var TableDiff[]
43
     */
44
    public $changedTables = [];
45 46

    /**
Benjamin Morel's avatar
Benjamin Morel committed
47
     * All removed tables.
48
     *
49
     * @var Table[]
50
     */
51
    public $removedTables = [];
52

53
    /** @var Sequence[] */
54
    public $newSequences = [];
55

56
    /** @var Sequence[] */
57
    public $changedSequences = [];
58

59
    /** @var Sequence[] */
60
    public $removedSequences = [];
61

62
    /** @var ForeignKeyConstraint[] */
63
    public $orphanedForeignKeys = [];
64

65 66 67
    /**
     * Constructs an SchemaDiff object.
     *
68 69 70
     * @param Table[]     $newTables
     * @param TableDiff[] $changedTables
     * @param Table[]     $removedTables
71
     */
72
    public function __construct($newTables = [], $changedTables = [], $removedTables = [], ?Schema $fromSchema = null)
73
    {
Benjamin Morel's avatar
Benjamin Morel committed
74
        $this->newTables     = $newTables;
75 76
        $this->changedTables = $changedTables;
        $this->removedTables = $removedTables;
Benjamin Morel's avatar
Benjamin Morel committed
77
        $this->fromSchema    = $fromSchema;
78
    }
79 80

    /**
81 82 83 84 85 86 87 88
     * The to save sql mode ensures that the following things don't happen:
     *
     * 1. Tables are deleted
     * 2. Sequences are deleted
     * 3. Foreign Keys which reference tables that would otherwise be deleted.
     *
     * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all.
     *
89
     * @return string[]
90 91 92 93 94 95 96
     */
    public function toSaveSql(AbstractPlatform $platform)
    {
        return $this->_toSql($platform, true);
    }

    /**
97
     * @return string[]
98 99
     */
    public function toSql(AbstractPlatform $platform)
100 101 102 103 104
    {
        return $this->_toSql($platform, false);
    }

    /**
105
     * @param bool $saveMode
Benjamin Morel's avatar
Benjamin Morel committed
106
     *
107
     * @return string[]
108 109
     */
    protected function _toSql(AbstractPlatform $platform, $saveMode = false)
110
    {
111
        $sql = [];
112

Marco Pivetta's avatar
Marco Pivetta committed
113 114 115 116 117 118
        if ($platform->supportsSchemas()) {
            foreach ($this->newNamespaces as $newNamespace) {
                $sql[] = $platform->getCreateSchemaSQL($newNamespace);
            }
        }

119
        if ($platform->supportsForeignKeyConstraints() && $saveMode === false) {
120
            foreach ($this->orphanedForeignKeys as $orphanedForeignKey) {
121
                $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable());
122 123 124
            }
        }

125
        if ($platform->supportsSequences() === true) {
126
            foreach ($this->changedSequences as $sequence) {
127
                $sql[] = $platform->getAlterSequenceSQL($sequence);
128 129
            }

130
            if ($saveMode === false) {
131
                foreach ($this->removedSequences as $sequence) {
132
                    $sql[] = $platform->getDropSequenceSQL($sequence);
133
                }
134 135
            }

136
            foreach ($this->newSequences as $sequence) {
137
                $sql[] = $platform->getCreateSequenceSQL($sequence);
138 139 140
            }
        }

141
        $foreignKeySql = [];
142 143 144 145 146
        $createFlags   = AbstractPlatform::CREATE_INDEXES;

        if (! $platform->supportsCreateDropForeignKeyConstraints()) {
            $createFlags |= AbstractPlatform::CREATE_FOREIGNKEYS;
        }
147

148 149 150 151
        foreach ($this->getNewTablesSortedByDependencies() as $table) {
            $sql = array_merge($sql, $platform->getCreateTableSQL($table, $createFlags));

            if (! $platform->supportsCreateDropForeignKeyConstraints()) {
152 153 154 155 156
                continue;
            }

            foreach ($table->getForeignKeys() as $foreignKey) {
                $foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table);
157
            }
158
        }
Grégoire Paris's avatar
Grégoire Paris committed
159

160
        $sql = array_merge($sql, $foreignKeySql);
161

162
        if ($saveMode === false) {
163
            foreach ($this->removedTables as $table) {
164
                $sql[] = $platform->getDropTableSQL($table);
165
            }
166 167
        }

168
        foreach ($this->changedTables as $tableDiff) {
169
            $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff));
170 171 172 173
        }

        return $sql;
    }
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

    /**
     * Sorts tables by dependencies so that they are created in the right order.
     *
     * This is necessary when one table depends on another while creating foreign key
     * constraints directly during CREATE TABLE.
     *
     * @return array<Table>
     */
    private function getNewTablesSortedByDependencies()
    {
        $calculator = new DependencyOrderCalculator();
        $newTables  = [];

        foreach ($this->newTables as $table) {
            $newTables[$table->getName()] = true;
            $calculator->addNode($table->getName(), $table);
        }

        foreach ($this->newTables as $table) {
            foreach ($table->getForeignKeys() as $foreignKey) {
                $foreignTableName = $foreignKey->getForeignTableName();

                if (! isset($newTables[$foreignTableName])) {
                    continue;
                }

                $calculator->addDependency($foreignTableName, $table->getName());
            }
        }

        return $calculator->sort();
    }
207
}