Schema.php 14 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
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
24
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
25
use Doctrine\DBAL\Schema\Visitor\Visitor;
Benjamin Morel's avatar
Benjamin Morel committed
26
use Doctrine\DBAL\Platforms\AbstractPlatform;
27 28 29
use function array_keys;
use function strpos;
use function strtolower;
30 31

/**
Benjamin Morel's avatar
Benjamin Morel committed
32
 * Object representation of a database schema.
33
 *
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
 * 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
55 56 57
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Benjamin Eberlei <kontakt@beberlei.de>
58 59 60
 */
class Schema extends AbstractAsset
{
61 62 63 64 65
    /**
     * The namespaces in this schema.
     *
     * @var array
     */
66
    private $namespaces = [];
67

68
    /**
Benjamin Morel's avatar
Benjamin Morel committed
69
     * @var \Doctrine\DBAL\Schema\Table[]
70
     */
71
    protected $_tables = [];
72

73
    /**
Benjamin Morel's avatar
Benjamin Morel committed
74
     * @var \Doctrine\DBAL\Schema\Sequence[]
75
     */
76
    protected $_sequences = [];
77

78
    /**
79
     * @var SchemaConfig
80
     */
81
    protected $_schemaConfig = false;
82

83
    /**
Benjamin Morel's avatar
Benjamin Morel committed
84 85 86
     * @param \Doctrine\DBAL\Schema\Table[]      $tables
     * @param \Doctrine\DBAL\Schema\Sequence[]   $sequences
     * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig
87
     * @param array                              $namespaces
88
     */
89
    public function __construct(
90 91
        array $tables = [],
        array $sequences = [],
92
        SchemaConfig $schemaConfig = null,
93
        array $namespaces = []
94
    ) {
95 96 97 98
        if ($schemaConfig == null) {
            $schemaConfig = new SchemaConfig();
        }
        $this->_schemaConfig = $schemaConfig;
99
        $this->_setName($schemaConfig->getName() ?: 'public');
100

101 102 103 104
        foreach ($namespaces as $namespace) {
            $this->createNamespace($namespace);
        }

105
        foreach ($tables as $table) {
106 107
            $this->_addTable($table);
        }
108

109
        foreach ($sequences as $sequence) {
110 111
            $this->_addSequence($sequence);
        }
112 113 114
    }

    /**
115
     * @return bool
116 117 118
     */
    public function hasExplicitForeignKeyIndexes()
    {
119
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
120 121 122
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
123 124 125 126 127
     * @param \Doctrine\DBAL\Schema\Table $table
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
128 129 130
     */
    protected function _addTable(Table $table)
    {
131
        $namespaceName = $table->getNamespaceName();
132
        $tableName = $table->getFullQualifiedName($this->getName());
133

Steve Müller's avatar
Steve Müller committed
134
        if (isset($this->_tables[$tableName])) {
135 136 137
            throw SchemaException::tableAlreadyExists($tableName);
        }

138 139 140 141
        if ( ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
            $this->createNamespace($namespaceName);
        }

142
        $this->_tables[$tableName] = $table;
143
        $table->setSchemaConfig($this->_schemaConfig);
144 145 146
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
147 148 149 150 151
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
152 153 154
     */
    protected function _addSequence(Sequence $sequence)
    {
155
        $namespaceName = $sequence->getNamespaceName();
156
        $seqName = $sequence->getFullQualifiedName($this->getName());
157

158 159 160
        if (isset($this->_sequences[$seqName])) {
            throw SchemaException::sequenceAlreadyExists($seqName);
        }
161 162 163 164 165

        if ( ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
            $this->createNamespace($namespaceName);
        }

166 167 168
        $this->_sequences[$seqName] = $sequence;
    }

169 170 171 172 173 174 175 176 177 178
    /**
     * Returns the namespaces of this schema.
     *
     * @return array A list of namespace names.
     */
    public function getNamespaces()
    {
        return $this->namespaces;
    }

179
    /**
Benjamin Morel's avatar
Benjamin Morel committed
180
     * Gets all tables of this schema.
181
     *
Benjamin Morel's avatar
Benjamin Morel committed
182
     * @return \Doctrine\DBAL\Schema\Table[]
183 184 185 186 187 188 189 190
     */
    public function getTables()
    {
        return $this->_tables;
    }

    /**
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
191 192 193 194
     *
     * @return \Doctrine\DBAL\Schema\Table
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
195 196 197
     */
    public function getTable($tableName)
    {
198
        $tableName = $this->getFullQualifiedAssetName($tableName);
199 200 201 202 203 204 205
        if (!isset($this->_tables[$tableName])) {
            throw SchemaException::tableDoesNotExist($tableName);
        }

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

206
    /**
Benjamin Morel's avatar
Benjamin Morel committed
207 208
     * @param string $name
     *
209 210 211 212
     * @return string
     */
    private function getFullQualifiedAssetName($name)
    {
213 214
        $name = $this->getUnquotedAssetName($name);

215 216 217
        if (strpos($name, ".") === false) {
            $name = $this->getName() . "." . $name;
        }
Benjamin Morel's avatar
Benjamin Morel committed
218

219 220 221
        return strtolower($name);
    }

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
    /**
     * Returns the unquoted representation of a given asset name.
     *
     * @param string $assetName Quoted or unquoted representation of an asset name.
     *
     * @return string
     */
    private function getUnquotedAssetName($assetName)
    {
        if ($this->isIdentifierQuoted($assetName)) {
            return $this->trimQuotes($assetName);
        }

        return $assetName;
    }

Marco Pivetta's avatar
Marco Pivetta committed
238 239 240 241 242
    /**
     * Does this schema have a namespace with the given name?
     *
     * @param string $namespaceName
     *
243
     * @return bool
Marco Pivetta's avatar
Marco Pivetta committed
244 245 246
     */
    public function hasNamespace($namespaceName)
    {
247
        $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
Marco Pivetta's avatar
Marco Pivetta committed
248

249
        return isset($this->namespaces[$namespaceName]);
Marco Pivetta's avatar
Marco Pivetta committed
250 251
    }

252 253
    /**
     * Does this schema have a table with the given name?
254
     *
Benjamin Morel's avatar
Benjamin Morel committed
255 256
     * @param string $tableName
     *
257
     * @return bool
258 259 260
     */
    public function hasTable($tableName)
    {
261
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
262

263 264 265
        return isset($this->_tables[$tableName]);
    }

266
    /**
Benjamin Morel's avatar
Benjamin Morel committed
267
     * Gets all table names, prefixed with a schema name, even the default one if present.
268 269 270
     *
     * @return array
     */
271
    public function getTableNames()
272
    {
273
        return array_keys($this->_tables);
274 275
    }

Benjamin Morel's avatar
Benjamin Morel committed
276 277 278
    /**
     * @param string $sequenceName
     *
279
     * @return bool
Benjamin Morel's avatar
Benjamin Morel committed
280
     */
281 282
    public function hasSequence($sequenceName)
    {
283
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Benjamin Morel's avatar
Benjamin Morel committed
284

285 286 287 288
        return isset($this->_sequences[$sequenceName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
289 290
     * @param string $sequenceName
     *
291
     * @return \Doctrine\DBAL\Schema\Sequence
Benjamin Morel's avatar
Benjamin Morel committed
292 293
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
294 295 296
     */
    public function getSequence($sequenceName)
    {
297
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Steve Müller's avatar
Steve Müller committed
298
        if (!$this->hasSequence($sequenceName)) {
299 300
            throw SchemaException::sequenceDoesNotExist($sequenceName);
        }
Benjamin Morel's avatar
Benjamin Morel committed
301

302 303 304 305
        return $this->_sequences[$sequenceName];
    }

    /**
306
     * @return \Doctrine\DBAL\Schema\Sequence[]
307 308 309 310 311 312
     */
    public function getSequences()
    {
        return $this->_sequences;
    }

313 314 315 316 317 318
    /**
     * Creates a new namespace.
     *
     * @param string $namespaceName The name of the namespace to create.
     *
     * @return \Doctrine\DBAL\Schema\Schema This schema instance.
319 320
     *
     * @throws SchemaException
321 322 323 324 325 326 327 328 329 330 331 332 333 334
     */
    public function createNamespace($namespaceName)
    {
        $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));

        if (isset($this->namespaces[$unquotedNamespaceName])) {
            throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
        }

        $this->namespaces[$unquotedNamespaceName] = $namespaceName;

        return $this;
    }

335
    /**
Benjamin Morel's avatar
Benjamin Morel committed
336 337 338
     * Creates a new table.
     *
     * @param string $tableName
339
     *
Benjamin Morel's avatar
Benjamin Morel committed
340
     * @return \Doctrine\DBAL\Schema\Table
341 342 343 344 345
     */
    public function createTable($tableName)
    {
        $table = new Table($tableName);
        $this->_addTable($table);
346 347 348 349 350

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

351 352 353 354
        return $table;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
355
     * Renames a table.
356 357 358
     *
     * @param string $oldTableName
     * @param string $newTableName
Benjamin Morel's avatar
Benjamin Morel committed
359 360
     *
     * @return \Doctrine\DBAL\Schema\Schema
361 362 363 364 365 366 367 368
     */
    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
369

370 371 372 373
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
374
     * Drops a table from the schema.
375 376
     *
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
377 378
     *
     * @return \Doctrine\DBAL\Schema\Schema
379 380 381
     */
    public function dropTable($tableName)
    {
382
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
383
        $this->getTable($tableName);
384
        unset($this->_tables[$tableName]);
Benjamin Morel's avatar
Benjamin Morel committed
385

386 387 388 389
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
390 391
     * Creates a new sequence.
     *
392 393 394
     * @param string $sequenceName
     * @param int    $allocationSize
     * @param int    $initialValue
395
     *
Benjamin Morel's avatar
Benjamin Morel committed
396
     * @return \Doctrine\DBAL\Schema\Sequence
397 398 399 400 401
     */
    public function createSequence($sequenceName, $allocationSize=1, $initialValue=1)
    {
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
        $this->_addSequence($seq);
Benjamin Morel's avatar
Benjamin Morel committed
402

403
        return $seq;
404 405 406 407
    }

    /**
     * @param string $sequenceName
Benjamin Morel's avatar
Benjamin Morel committed
408 409
     *
     * @return \Doctrine\DBAL\Schema\Schema
410 411 412
     */
    public function dropSequence($sequenceName)
    {
413
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
414
        unset($this->_sequences[$sequenceName]);
Benjamin Morel's avatar
Benjamin Morel committed
415

416 417 418 419
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
420
     * Returns an array of necessary SQL queries to create the schema on the given platform.
421
     *
422
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
423
     *
424
     * @return array
425
     */
Benjamin Morel's avatar
Benjamin Morel committed
426
    public function toSql(AbstractPlatform $platform)
427 428 429 430 431 432 433
    {
        $sqlCollector = new CreateSchemaSqlCollector($platform);
        $this->visit($sqlCollector);

        return $sqlCollector->getQueries();
    }

434
    /**
Benjamin Morel's avatar
Benjamin Morel committed
435
     * Return an array of necessary SQL queries to drop the schema on the given platform.
436
     *
437
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
438
     *
439 440
     * @return array
     */
Benjamin Morel's avatar
Benjamin Morel committed
441
    public function toDropSql(AbstractPlatform $platform)
442 443 444 445 446 447 448
    {
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
        $this->visit($dropSqlCollector);

        return $dropSqlCollector->getQueries();
    }

449
    /**
Benjamin Morel's avatar
Benjamin Morel committed
450
     * @param \Doctrine\DBAL\Schema\Schema              $toSchema
451
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
452 453
     *
     * @return array
454
     */
Benjamin Morel's avatar
Benjamin Morel committed
455
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
456
    {
457
        $comparator = new Comparator();
458
        $schemaDiff = $comparator->compare($this, $toSchema);
Benjamin Morel's avatar
Benjamin Morel committed
459

460
        return $schemaDiff->toSql($platform);
461 462
    }

463
    /**
Benjamin Morel's avatar
Benjamin Morel committed
464
     * @param \Doctrine\DBAL\Schema\Schema              $fromSchema
465
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
466 467
     *
     * @return array
468
     */
Benjamin Morel's avatar
Benjamin Morel committed
469
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
470
    {
471
        $comparator = new Comparator();
472
        $schemaDiff = $comparator->compare($fromSchema, $this);
Benjamin Morel's avatar
Benjamin Morel committed
473

474
        return $schemaDiff->toSql($platform);
475 476
    }

477
    /**
Benjamin Morel's avatar
Benjamin Morel committed
478 479 480
     * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor
     *
     * @return void
481 482 483 484
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptSchema($this);
485

486 487 488 489 490 491
        if ($visitor instanceof NamespaceVisitor) {
            foreach ($this->namespaces as $namespace) {
                $visitor->acceptNamespace($namespace);
            }
        }

492
        foreach ($this->_tables as $table) {
493 494
            $table->visit($visitor);
        }
495

496
        foreach ($this->_sequences as $sequence) {
497 498 499
            $sequence->visit($visitor);
        }
    }
500 501 502 503 504 505 506 507

    /**
     * Cloning a Schema triggers a deep clone of all related assets.
     *
     * @return void
     */
    public function __clone()
    {
508
        foreach ($this->_tables as $k => $table) {
509 510
            $this->_tables[$k] = clone $table;
        }
511
        foreach ($this->_sequences as $k => $sequence) {
512 513 514
            $this->_sequences[$k] = clone $sequence;
        }
    }
515
}