Schema.php 11.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * 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
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17 18 19 20 21 22
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\DBAL\Schema;

use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
23 24
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\Visitor;
Benjamin Morel's avatar
Benjamin Morel committed
25
use Doctrine\DBAL\Platforms\AbstractPlatform;
26 27

/**
Benjamin Morel's avatar
Benjamin Morel committed
28
 * Object representation of a database schema.
29
 *
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
 * Different vendors have very inconsistent naming with regard to the concept
 * of a "schema". Doctrine understands a schema as the entity that conceptually
 * wraps a set of database objects such as tables, sequences, indexes and
 * foreign keys that belong to each other into a namespace. A Doctrine Schema
 * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
 * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
 *
 * Every asset in the doctrine schema has a name. A name consists of either a
 * namespace.local name pair or just a local unqualified name.
 *
 * The abstraction layer that covers a PostgreSQL schema is the namespace of an
 * database object (asset). A schema can have a name, which will be used as
 * default namespace for the unqualified database objects that are created in
 * the schema.
 *
 * In the case of MySQL where cross-database queries are allowed this leads to
 * databases being "misinterpreted" as namespaces. This is intentional, however
 * the CREATE/DROP SQL visitors will just filter this queries and do not
 * execute them. Only the queries for the currently connected database are
 * executed.
 *
Benjamin Morel's avatar
Benjamin Morel committed
51 52 53
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Benjamin Eberlei <kontakt@beberlei.de>
54 55 56 57
 */
class Schema extends AbstractAsset
{
    /**
Benjamin Morel's avatar
Benjamin Morel committed
58
     * @var \Doctrine\DBAL\Schema\Table[]
59 60
     */
    protected $_tables = array();
61

62
    /**
Benjamin Morel's avatar
Benjamin Morel committed
63
     * @var \Doctrine\DBAL\Schema\Sequence[]
64 65 66
     */
    protected $_sequences = array();

67
    /**
Benjamin Morel's avatar
Benjamin Morel committed
68
     * @var \Doctrine\DBAL\Schema\SchemaConfig
69
     */
70
    protected $_schemaConfig = false;
71

72
    /**
Benjamin Morel's avatar
Benjamin Morel committed
73 74 75
     * @param \Doctrine\DBAL\Schema\Table[]      $tables
     * @param \Doctrine\DBAL\Schema\Sequence[]   $sequences
     * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig
76
     */
77
    public function __construct(array $tables=array(), array $sequences=array(), SchemaConfig $schemaConfig=null)
78
    {
79 80 81 82
        if ($schemaConfig == null) {
            $schemaConfig = new SchemaConfig();
        }
        $this->_schemaConfig = $schemaConfig;
83
        $this->_setName($schemaConfig->getName() ?: 'public');
84

85
        foreach ($tables as $table) {
86 87
            $this->_addTable($table);
        }
88

89
        foreach ($sequences as $sequence) {
90 91
            $this->_addSequence($sequence);
        }
92 93 94
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
95
     * @return boolean
96 97 98
     */
    public function hasExplicitForeignKeyIndexes()
    {
99
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
100 101 102
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
103 104 105 106 107
     * @param \Doctrine\DBAL\Schema\Table $table
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
108 109 110
     */
    protected function _addTable(Table $table)
    {
111
        $tableName = $table->getFullQualifiedName($this->getName());
Steve Müller's avatar
Steve Müller committed
112
        if (isset($this->_tables[$tableName])) {
113 114 115 116
            throw SchemaException::tableAlreadyExists($tableName);
        }

        $this->_tables[$tableName] = $table;
117
        $table->setSchemaConfig($this->_schemaConfig);
118 119 120
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
121 122 123 124 125
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
126 127 128
     */
    protected function _addSequence(Sequence $sequence)
    {
129
        $seqName = $sequence->getFullQualifiedName($this->getName());
130 131 132 133 134 135 136
        if (isset($this->_sequences[$seqName])) {
            throw SchemaException::sequenceAlreadyExists($seqName);
        }
        $this->_sequences[$seqName] = $sequence;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
137
     * Gets all tables of this schema.
138
     *
Benjamin Morel's avatar
Benjamin Morel committed
139
     * @return \Doctrine\DBAL\Schema\Table[]
140 141 142 143 144 145 146 147
     */
    public function getTables()
    {
        return $this->_tables;
    }

    /**
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
148 149 150 151
     *
     * @return \Doctrine\DBAL\Schema\Table
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
152 153 154
     */
    public function getTable($tableName)
    {
155
        $tableName = $this->getFullQualifiedAssetName($tableName);
156 157 158 159 160 161 162
        if (!isset($this->_tables[$tableName])) {
            throw SchemaException::tableDoesNotExist($tableName);
        }

        return $this->_tables[$tableName];
    }

163
    /**
Benjamin Morel's avatar
Benjamin Morel committed
164 165
     * @param string $name
     *
166 167 168 169
     * @return string
     */
    private function getFullQualifiedAssetName($name)
    {
170
        if ($this->isIdentifierQuoted($name)) {
171 172
            $name = $this->trimQuotes($name);
        }
173 174 175
        if (strpos($name, ".") === false) {
            $name = $this->getName() . "." . $name;
        }
Benjamin Morel's avatar
Benjamin Morel committed
176

177 178 179
        return strtolower($name);
    }

180 181
    /**
     * Does this schema have a table with the given name?
182
     *
Benjamin Morel's avatar
Benjamin Morel committed
183 184 185
     * @param string $tableName
     *
     * @return boolean
186 187 188
     */
    public function hasTable($tableName)
    {
189
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
190

191 192 193
        return isset($this->_tables[$tableName]);
    }

194
    /**
Benjamin Morel's avatar
Benjamin Morel committed
195
     * Gets all table names, prefixed with a schema name, even the default one if present.
196 197 198
     *
     * @return array
     */
199
    public function getTableNames()
200
    {
201
        return array_keys($this->_tables);
202 203
    }

Benjamin Morel's avatar
Benjamin Morel committed
204 205 206 207 208
    /**
     * @param string $sequenceName
     *
     * @return boolean
     */
209 210
    public function hasSequence($sequenceName)
    {
211
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Benjamin Morel's avatar
Benjamin Morel committed
212

213 214 215 216
        return isset($this->_sequences[$sequenceName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
217 218
     * @param string $sequenceName
     *
219
     * @return \Doctrine\DBAL\Schema\Sequence
Benjamin Morel's avatar
Benjamin Morel committed
220 221
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
222 223 224
     */
    public function getSequence($sequenceName)
    {
225
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Steve Müller's avatar
Steve Müller committed
226
        if (!$this->hasSequence($sequenceName)) {
227 228
            throw SchemaException::sequenceDoesNotExist($sequenceName);
        }
Benjamin Morel's avatar
Benjamin Morel committed
229

230 231 232 233
        return $this->_sequences[$sequenceName];
    }

    /**
234
     * @return \Doctrine\DBAL\Schema\Sequence[]
235 236 237 238 239 240 241
     */
    public function getSequences()
    {
        return $this->_sequences;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
242 243 244
     * Creates a new table.
     *
     * @param string $tableName
245
     *
Benjamin Morel's avatar
Benjamin Morel committed
246
     * @return \Doctrine\DBAL\Schema\Table
247 248 249 250 251
     */
    public function createTable($tableName)
    {
        $table = new Table($tableName);
        $this->_addTable($table);
252 253 254 255 256

        foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
            $table->addOption($name, $value);
        }

257 258 259 260
        return $table;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
261
     * Renames a table.
262 263 264
     *
     * @param string $oldTableName
     * @param string $newTableName
Benjamin Morel's avatar
Benjamin Morel committed
265 266
     *
     * @return \Doctrine\DBAL\Schema\Schema
267 268 269 270 271 272 273 274
     */
    public function renameTable($oldTableName, $newTableName)
    {
        $table = $this->getTable($oldTableName);
        $table->_setName($newTableName);

        $this->dropTable($oldTableName);
        $this->_addTable($table);
Benjamin Morel's avatar
Benjamin Morel committed
275

276 277 278 279
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
280
     * Drops a table from the schema.
281 282
     *
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
283 284
     *
     * @return \Doctrine\DBAL\Schema\Schema
285 286 287
     */
    public function dropTable($tableName)
    {
288
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
289
        $this->getTable($tableName);
290
        unset($this->_tables[$tableName]);
Benjamin Morel's avatar
Benjamin Morel committed
291

292 293 294 295
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
296 297 298 299 300
     * Creates a new sequence.
     *
     * @param string  $sequenceName
     * @param integer $allocationSize
     * @param integer $initialValue
301
     *
Benjamin Morel's avatar
Benjamin Morel committed
302
     * @return \Doctrine\DBAL\Schema\Sequence
303 304 305 306 307
     */
    public function createSequence($sequenceName, $allocationSize=1, $initialValue=1)
    {
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
        $this->_addSequence($seq);
Benjamin Morel's avatar
Benjamin Morel committed
308

309
        return $seq;
310 311 312 313
    }

    /**
     * @param string $sequenceName
Benjamin Morel's avatar
Benjamin Morel committed
314 315
     *
     * @return \Doctrine\DBAL\Schema\Schema
316 317 318
     */
    public function dropSequence($sequenceName)
    {
319
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
320
        unset($this->_sequences[$sequenceName]);
Benjamin Morel's avatar
Benjamin Morel committed
321

322 323 324 325
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
326
     * Returns an array of necessary SQL queries to create the schema on the given platform.
327
     *
328
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
329
     *
330
     * @return array
331
     */
Benjamin Morel's avatar
Benjamin Morel committed
332
    public function toSql(AbstractPlatform $platform)
333 334 335 336 337 338 339
    {
        $sqlCollector = new CreateSchemaSqlCollector($platform);
        $this->visit($sqlCollector);

        return $sqlCollector->getQueries();
    }

340
    /**
Benjamin Morel's avatar
Benjamin Morel committed
341
     * Return an array of necessary SQL queries to drop the schema on the given platform.
342
     *
343
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
344
     *
345 346
     * @return array
     */
Benjamin Morel's avatar
Benjamin Morel committed
347
    public function toDropSql(AbstractPlatform $platform)
348 349 350 351 352 353 354
    {
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
        $this->visit($dropSqlCollector);

        return $dropSqlCollector->getQueries();
    }

355
    /**
Benjamin Morel's avatar
Benjamin Morel committed
356
     * @param \Doctrine\DBAL\Schema\Schema              $toSchema
357
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
358 359
     *
     * @return array
360
     */
Benjamin Morel's avatar
Benjamin Morel committed
361
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
362
    {
363
        $comparator = new Comparator();
364
        $schemaDiff = $comparator->compare($this, $toSchema);
Benjamin Morel's avatar
Benjamin Morel committed
365

366
        return $schemaDiff->toSql($platform);
367 368
    }

369
    /**
Benjamin Morel's avatar
Benjamin Morel committed
370
     * @param \Doctrine\DBAL\Schema\Schema              $fromSchema
371
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
372 373
     *
     * @return array
374
     */
Benjamin Morel's avatar
Benjamin Morel committed
375
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
376
    {
377
        $comparator = new Comparator();
378
        $schemaDiff = $comparator->compare($fromSchema, $this);
Benjamin Morel's avatar
Benjamin Morel committed
379

380
        return $schemaDiff->toSql($platform);
381 382
    }

383
    /**
Benjamin Morel's avatar
Benjamin Morel committed
384 385 386
     * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor
     *
     * @return void
387 388 389 390
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptSchema($this);
391

392
        foreach ($this->_tables as $table) {
393 394
            $table->visit($visitor);
        }
395
        foreach ($this->_sequences as $sequence) {
396 397 398
            $sequence->visit($visitor);
        }
    }
399 400 401 402 403 404 405 406

    /**
     * Cloning a Schema triggers a deep clone of all related assets.
     *
     * @return void
     */
    public function __clone()
    {
407
        foreach ($this->_tables as $k => $table) {
408 409
            $this->_tables[$k] = clone $table;
        }
410
        foreach ($this->_sequences as $k => $sequence) {
411 412 413
            $this->_sequences[$k] = clone $sequence;
        }
    }
414
}