Table.php 24.3 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\Types\Type;
23
use Doctrine\DBAL\Schema\Visitor\Visitor;
24
use Doctrine\DBAL\DBALException;
25 26

/**
Benjamin Morel's avatar
Benjamin Morel committed
27
 * Object Representation of a table.
28
 *
Benjamin Morel's avatar
Benjamin Morel committed
29 30 31
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Benjamin Eberlei <kontakt@beberlei.de>
32 33 34 35 36 37 38 39 40
 */
class Table extends AbstractAsset
{
    /**
     * @var string
     */
    protected $_name = null;

    /**
41
     * @var Column[]
42 43 44
     */
    protected $_columns = array();

45 46 47 48 49
    /**
     * @var Index[]
     */
    private $implicitIndexes = array();

50
    /**
51
     * @var Index[]
52 53 54 55 56 57 58 59 60
     */
    protected $_indexes = array();

    /**
     * @var string
     */
    protected $_primaryKeyName = false;

    /**
61
     * @var ForeignKeyConstraint[]
62
     */
63
    protected $_fkConstraints = array();
64 65 66 67 68 69

    /**
     * @var array
     */
    protected $_options = array();

70
    /**
71
     * @var SchemaConfig
72 73 74
     */
    protected $_schemaConfig = null;

75
    /**
76 77 78 79 80 81
     * @param string                 $tableName
     * @param Column[]               $columns
     * @param Index[]                $indexes
     * @param ForeignKeyConstraint[] $fkConstraints
     * @param integer                $idGeneratorType
     * @param array                  $options
82
     *
83
     * @throws DBALException
84
     */
85
    public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array())
86
    {
87 88 89 90
        if (strlen($tableName) == 0) {
            throw DBALException::invalidTableName($tableName);
        }

91
        $this->_setName($tableName);
92

93
        foreach ($columns as $column) {
94 95
            $this->_addColumn($column);
        }
96

97
        foreach ($indexes as $idx) {
98 99 100
            $this->_addIndex($idx);
        }

101
        foreach ($fkConstraints as $constraint) {
102
            $this->_addForeignKeyConstraint($constraint);
103 104 105 106 107
        }

        $this->_options = $options;
    }

108
    /**
109
     * @param SchemaConfig $schemaConfig
Benjamin Morel's avatar
Benjamin Morel committed
110 111
     *
     * @return void
112 113 114 115 116 117 118
     */
    public function setSchemaConfig(SchemaConfig $schemaConfig)
    {
        $this->_schemaConfig = $schemaConfig;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
119
     * @return integer
120 121 122 123 124 125 126 127 128 129
     */
    protected function _getMaxIdentifierLength()
    {
        if ($this->_schemaConfig instanceof SchemaConfig) {
            return $this->_schemaConfig->getMaxIdentifierLength();
        } else {
            return 63;
        }
    }

130
    /**
Benjamin Morel's avatar
Benjamin Morel committed
131
     * Sets the Primary Key.
132
     *
Benjamin Morel's avatar
Benjamin Morel committed
133 134 135
     * @param array          $columns
     * @param string|boolean $indexName
     *
136
     * @return self
137
     */
138
    public function setPrimaryKey(array $columns, $indexName = false)
139
    {
140
        $this->_addIndex($this->_createIndex($columns, $indexName ?: "primary", true, true));
141

142
        foreach ($columns as $columnName) {
143 144 145 146
            $column = $this->getColumn($columnName);
            $column->setNotnull(true);
        }

147
        return $this;
148 149 150
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
151 152 153
     * @param array       $columnNames
     * @param string|null $indexName
     * @param array       $flags
154
     * @param array       $options
Benjamin Morel's avatar
Benjamin Morel committed
155
     *
156
     * @return self
157
     */
158
    public function addIndex(array $columnNames, $indexName = null, array $flags = array(), array $options = array())
159
    {
Steve Müller's avatar
Steve Müller committed
160
        if ($indexName == null) {
161
            $indexName = $this->_generateIdentifierName(
162
                array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
163
            );
164 165
        }

166
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
167 168
    }

169
    /**
Benjamin Morel's avatar
Benjamin Morel committed
170
     * Drops the primary key from this table.
171 172 173 174 175 176
     *
     * @return void
     */
    public function dropPrimaryKey()
    {
        $this->dropIndex($this->_primaryKeyName);
177
        $this->_primaryKeyName = false;
178 179 180
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
181 182 183
     * Drops an index from this table.
     *
     * @param string $indexName The index name.
184 185
     *
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
186
     *
187
     * @throws SchemaException If the index does not exist.
188 189 190
     */
    public function dropIndex($indexName)
    {
191
        $indexName = $this->normalizeIdentifier($indexName);
192
        if ( ! $this->hasIndex($indexName)) {
193 194 195 196 197
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
        }
        unset($this->_indexes[$indexName]);
    }

198
    /**
Benjamin Morel's avatar
Benjamin Morel committed
199 200
     * @param array       $columnNames
     * @param string|null $indexName
201
     * @param array       $options
202
     *
203
     * @return self
204
     */
205
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = array())
206
    {
207
        if ($indexName === null) {
208
            $indexName = $this->_generateIdentifierName(
209
                array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
210
            );
211 212
        }

213
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, array(), $options));
214 215
    }

216 217 218 219 220 221 222
    /**
     * Renames an index.
     *
     * @param string      $oldIndexName The name of the index to rename from.
     * @param string|null $newIndexName The name of the index to rename to.
     *                                  If null is given, the index name will be auto-generated.
     *
223
     * @return self This table instance.
224 225 226 227 228 229
     *
     * @throws SchemaException if no index exists for the given current name
     *                         or if an index with the given new name already exists on this table.
     */
    public function renameIndex($oldIndexName, $newIndexName = null)
    {
230 231
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

        if ($oldIndexName === $normalizedNewIndexName) {
            return $this;
        }

        if ( ! $this->hasIndex($oldIndexName)) {
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
        }

        if ($this->hasIndex($normalizedNewIndexName)) {
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
        }

        $oldIndex = $this->_indexes[$oldIndexName];

        if ($oldIndex->isPrimary()) {
            $this->dropPrimaryKey();

            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName);
        }

        unset($this->_indexes[$oldIndexName]);

        if ($oldIndex->isUnique()) {
256
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
257 258
        }

259
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
260 261
    }

262
    /**
Benjamin Morel's avatar
Benjamin Morel committed
263 264 265
     * Checks if an index begins in the order of the given columns.
     *
     * @param array $columnsNames
266
     *
Benjamin Morel's avatar
Benjamin Morel committed
267
     * @return boolean
268 269 270
     */
    public function columnsAreIndexed(array $columnsNames)
    {
271
        foreach ($this->getIndexes() as $index) {
272 273
            /* @var $index Index */
            if ($index->spansColumns($columnsNames)) {
274 275 276
                return true;
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
277

278 279 280
        return false;
    }

281
    /**
Benjamin Morel's avatar
Benjamin Morel committed
282 283 284 285 286
     * @param array   $columnNames
     * @param string  $indexName
     * @param boolean $isUnique
     * @param boolean $isPrimary
     * @param array   $flags
287
     * @param array   $options
288
     *
289
     * @return Index
Benjamin Morel's avatar
Benjamin Morel committed
290
     *
291
     * @throws SchemaException
292
     */
293
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array(), array $options = array())
294
    {
295
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
296 297 298
            throw SchemaException::indexNameInvalid($indexName);
        }

299
        foreach ($columnNames as $columnName => $indexColOptions) {
beberlei's avatar
beberlei committed
300 301 302 303
            if (is_numeric($columnName) && is_string($indexColOptions)) {
                $columnName = $indexColOptions;
            }

304
            if ( ! $this->hasColumn($columnName)) {
305
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
306 307
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
308

309
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
310 311 312 313
    }

    /**
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
314 315 316
     * @param string $typeName
     * @param array  $options
     *
317
     * @return Column
318
     */
319
    public function addColumn($columnName, $typeName, array $options=array())
320 321 322 323
    {
        $column = new Column($columnName, Type::getType($typeName), $options);

        $this->_addColumn($column);
Benjamin Morel's avatar
Benjamin Morel committed
324

325
        return $column;
326 327 328
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
329
     * Renames a Column.
330 331 332
     *
     * @param string $oldColumnName
     * @param string $newColumnName
Benjamin Morel's avatar
Benjamin Morel committed
333
     *
334
     * @deprecated
Benjamin Morel's avatar
Benjamin Morel committed
335
     *
336
     * @throws DBALException
337 338 339
     */
    public function renameColumn($oldColumnName, $newColumnName)
    {
340 341 342
        throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " .
            "the column instead. There is no fix available, because a schema diff cannot reliably detect if a " .
            "column was renamed or one column was created and another one dropped.");
343 344 345
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
346
     * Change Column Details.
347
     *
348
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
349 350
     * @param array  $options
     *
351
     * @return self
352 353 354 355 356
     */
    public function changeColumn($columnName, array $options)
    {
        $column = $this->getColumn($columnName);
        $column->setOptions($options);
Benjamin Morel's avatar
Benjamin Morel committed
357

358 359 360 361
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
362
     * Drops a Column from the Table.
363
     *
364
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
365
     *
366
     * @return self
367 368 369
     */
    public function dropColumn($columnName)
    {
370
        $columnName = $this->normalizeIdentifier($columnName);
371
        unset($this->_columns[$columnName]);
Benjamin Morel's avatar
Benjamin Morel committed
372

373 374 375 376
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
377
     * Adds a foreign key constraint.
378
     *
Benjamin Morel's avatar
Benjamin Morel committed
379
     * Name is inferred from the local columns.
380
     *
381 382 383 384 385
     * @param Table|string $foreignTable Table schema instance or table name
     * @param array        $localColumnNames
     * @param array        $foreignColumnNames
     * @param array        $options
     * @param string|null  $constraintName
Benjamin Morel's avatar
Benjamin Morel committed
386
     *
387
     * @return self
388
     */
389
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null)
390
    {
391
        $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
Benjamin Morel's avatar
Benjamin Morel committed
392

393
        return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
394 395
    }

396
    /**
Benjamin Morel's avatar
Benjamin Morel committed
397
     * Adds a foreign key constraint.
398
     *
Pascal Borreli's avatar
Pascal Borreli committed
399
     * Name is to be generated by the database itself.
400
     *
401
     * @deprecated Use {@link addForeignKeyConstraint}
Benjamin Morel's avatar
Benjamin Morel committed
402
     *
403 404 405 406
     * @param Table|string $foreignTable Table schema instance or table name
     * @param array        $localColumnNames
     * @param array        $foreignColumnNames
     * @param array        $options
Benjamin Morel's avatar
Benjamin Morel committed
407
     *
408
     * @return self
409 410 411
     */
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
    {
412
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
413 414
    }

415
    /**
Benjamin Morel's avatar
Benjamin Morel committed
416
     * Adds a foreign key constraint with a given name.
417
     *
418
     * @deprecated Use {@link addForeignKeyConstraint}
Benjamin Morel's avatar
Benjamin Morel committed
419
     *
420 421 422 423 424
     * @param string       $name
     * @param Table|string $foreignTable Table schema instance or table name
     * @param array        $localColumnNames
     * @param array        $foreignColumnNames
     * @param array        $options
Benjamin Morel's avatar
Benjamin Morel committed
425
     *
426
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
427
     *
428
     * @throws SchemaException
429 430
     */
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
431
    {
432
        if ($foreignTable instanceof Table) {
433
            foreach ($foreignColumnNames as $columnName) {
434
                if ( ! $foreignTable->hasColumn($columnName)) {
435
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
436
                }
437 438
            }
        }
439

440
        foreach ($localColumnNames as $columnName) {
441
            if ( ! $this->hasColumn($columnName)) {
442
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
443 444
            }
        }
445

446
        $constraint = new ForeignKeyConstraint(
447
            $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options
448
        );
449
        $this->_addForeignKeyConstraint($constraint);
450

451 452 453 454 455 456
        return $this;
    }

    /**
     * @param string $name
     * @param string $value
Benjamin Morel's avatar
Benjamin Morel committed
457
     *
458
     * @return self
459 460 461 462
     */
    public function addOption($name, $value)
    {
        $this->_options[$name] = $value;
Benjamin Morel's avatar
Benjamin Morel committed
463

464 465 466 467
        return $this;
    }

    /**
468
     * @param Column $column
Benjamin Morel's avatar
Benjamin Morel committed
469 470 471
     *
     * @return void
     *
472
     * @throws SchemaException
473 474 475
     */
    protected function _addColumn(Column $column)
    {
476
        $columnName = $column->getName();
477
        $columnName = $this->normalizeIdentifier($columnName);
478

479
        if (isset($this->_columns[$columnName])) {
480
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
481 482 483 484 485 486
        }

        $this->_columns[$columnName] = $column;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
487
     * Adds an index to the table.
488
     *
489
     * @param Index $indexCandidate
Benjamin Morel's avatar
Benjamin Morel committed
490
     *
491
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
492
     *
493
     * @throws SchemaException
494
     */
495
    protected function _addIndex(Index $indexCandidate)
496
    {
497
        $indexName = $indexCandidate->getName();
498
        $indexName = $this->normalizeIdentifier($indexName);
499
        $replacedImplicitIndexes = array();
500

501 502 503 504 505 506 507 508 509
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
            if ($implicitIndex->isFullfilledBy($indexCandidate) && isset($this->_indexes[$name])) {
                $replacedImplicitIndexes[] = $name;
            }
        }

        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
            ($this->_primaryKeyName != false && $indexCandidate->isPrimary())
        ) {
510
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
511 512
        }

513 514 515 516
        foreach ($replacedImplicitIndexes as $name) {
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
        }

517
        if ($indexCandidate->isPrimary()) {
518 519 520
            $this->_primaryKeyName = $indexName;
        }

521
        $this->_indexes[$indexName] = $indexCandidate;
Benjamin Morel's avatar
Benjamin Morel committed
522

523 524 525 526
        return $this;
    }

    /**
527
     * @param ForeignKeyConstraint $constraint
Benjamin Morel's avatar
Benjamin Morel committed
528 529
     *
     * @return void
530
     */
531
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
532
    {
533
        $constraint->setLocalTable($this);
534

Steve Müller's avatar
Steve Müller committed
535
        if (strlen($constraint->getName())) {
536 537 538
            $name = $constraint->getName();
        } else {
            $name = $this->_generateIdentifierName(
539
                array_merge((array) $this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
540 541
            );
        }
542
        $name = $this->normalizeIdentifier($name);
543 544

        $this->_fkConstraints[$name] = $constraint;
545

Pascal Borreli's avatar
Pascal Borreli committed
546
        // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
547
        // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
Pascal Borreli's avatar
Pascal Borreli committed
548
        // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
549 550 551 552 553 554 555 556 557 558 559 560 561
        $indexName = $this->_generateIdentifierName(
            array_merge(array($this->getName()), $constraint->getColumns()),
            "idx",
            $this->_getMaxIdentifierLength()
        );
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);

        foreach ($this->_indexes as $existingIndex) {
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
                return;
            }
        }

562 563
        $this->_addIndex($indexCandidate);
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
564 565 566
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
567 568 569 570 571
     * Returns whether this table has a foreign key constraint with the given name.
     *
     * @param string $constraintName
     *
     * @return boolean
572 573 574
     */
    public function hasForeignKey($constraintName)
    {
575
        $constraintName = $this->normalizeIdentifier($constraintName);
Benjamin Morel's avatar
Benjamin Morel committed
576

577 578 579 580
        return isset($this->_fkConstraints[$constraintName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
581 582 583 584
     * Returns the foreign key constraint with the given name.
     *
     * @param string $constraintName The constraint name.
     *
585
     * @return ForeignKeyConstraint
Benjamin Morel's avatar
Benjamin Morel committed
586
     *
587
     * @throws SchemaException If the foreign key does not exist.
588 589 590
     */
    public function getForeignKey($constraintName)
    {
591
        $constraintName = $this->normalizeIdentifier($constraintName);
Steve Müller's avatar
Steve Müller committed
592
        if (!$this->hasForeignKey($constraintName)) {
593
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
594 595 596
        }

        return $this->_fkConstraints[$constraintName];
597 598
    }

Benjamin Morel's avatar
Benjamin Morel committed
599 600 601 602 603 604 605
    /**
     * Removes the foreign key constraint with the given name.
     *
     * @param string $constraintName The constraint name.
     *
     * @return void
     *
606
     * @throws SchemaException
Benjamin Morel's avatar
Benjamin Morel committed
607
     */
608 609
    public function removeForeignKey($constraintName)
    {
610
        $constraintName = $this->normalizeIdentifier($constraintName);
Steve Müller's avatar
Steve Müller committed
611
        if (!$this->hasForeignKey($constraintName)) {
612 613 614 615 616 617
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
        }

        unset($this->_fkConstraints[$constraintName]);
    }

618
    /**
619
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
620
     * @return Column[]
621 622 623
     */
    public function getColumns()
    {
624 625 626 627
        $primaryKeyColumns = [];
        if ($this->hasPrimaryKey()) {
            $primaryKeyColumns = $this->filterColumns($this->getPrimaryKey()->getColumns());
        }
628

629
        return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns);
630
    }
631

632 633 634 635
    /**
     * Returns foreign key columns
     * @return Column[]
     */
636
    private function getForeignKeyColumns()
637
    {
638 639 640 641
        $foreignKeyColumns = [];
        foreach ($this->getForeignKeys() as $foreignKey) {
            /* @var $foreignKey ForeignKeyConstraint */
            $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns());
642
        }
643
        return $this->filterColumns($foreignKeyColumns);
644
    }
Benjamin Morel's avatar
Benjamin Morel committed
645

646
    /**
647 648 649
     * Returns only columns that have specified names
     * @param array $columnNames
     * @return Column[]
650
     */
651
    private function filterColumns(array $columnNames)
652
    {
653 654 655
        return array_filter($this->_columns, function ($columnName) use ($columnNames) {
            return in_array($columnName, $columnNames, true);
        }, ARRAY_FILTER_USE_KEY);
656 657 658
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
659
     * Returns whether this table has a Column with the given name.
660
     *
Benjamin Morel's avatar
Benjamin Morel committed
661 662 663
     * @param string $columnName The column name.
     *
     * @return boolean
664 665 666
     */
    public function hasColumn($columnName)
    {
667
        $columnName = $this->normalizeIdentifier($columnName);
Benjamin Morel's avatar
Benjamin Morel committed
668

669 670 671 672
        return isset($this->_columns[$columnName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
673 674 675
     * Returns the Column with the given name.
     *
     * @param string $columnName The column name.
676
     *
677
     * @return Column
Benjamin Morel's avatar
Benjamin Morel committed
678
     *
679
     * @throws SchemaException If the column does not exist.
680 681 682
     */
    public function getColumn($columnName)
    {
683
        $columnName = $this->normalizeIdentifier($columnName);
684
        if ( ! $this->hasColumn($columnName)) {
685
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
686 687 688 689 690 691
        }

        return $this->_columns[$columnName];
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
692 693
     * Returns the primary key.
     *
694
     * @return Index|null The primary key, or null if this Table has no primary key.
695 696 697
     */
    public function getPrimaryKey()
    {
698
        if ( ! $this->hasPrimaryKey()) {
699 700
            return null;
        }
Benjamin Morel's avatar
Benjamin Morel committed
701

702 703 704
        return $this->getIndex($this->_primaryKeyName);
    }

705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
    /**
     * Returns the primary key columns.
     *
     * @return array
     *
     * @throws DBALException
     */
    public function getPrimaryKeyColumns()
    {
        if ( ! $this->hasPrimaryKey()) {
            throw new DBALException("Table " . $this->getName() . " has no primary key.");
        }
        return $this->getPrimaryKey()->getColumns();
    }

720
    /**
Benjamin Morel's avatar
Benjamin Morel committed
721
     * Returns whether this table has a primary key.
722
     *
Benjamin Morel's avatar
Benjamin Morel committed
723
     * @return boolean
724 725 726 727 728 729
     */
    public function hasPrimaryKey()
    {
        return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
    }

730
    /**
Benjamin Morel's avatar
Benjamin Morel committed
731 732 733 734 735
     * Returns whether this table has an Index with the given name.
     *
     * @param string $indexName The index name.
     *
     * @return boolean
736 737 738
     */
    public function hasIndex($indexName)
    {
739
        $indexName = $this->normalizeIdentifier($indexName);
Benjamin Morel's avatar
Benjamin Morel committed
740

741 742 743 744
        return (isset($this->_indexes[$indexName]));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
745 746 747 748
     * Returns the Index with the given name.
     *
     * @param string $indexName The index name.
     *
749
     * @return Index
Benjamin Morel's avatar
Benjamin Morel committed
750
     *
751
     * @throws SchemaException If the index does not exist.
752 753 754
     */
    public function getIndex($indexName)
    {
755
        $indexName = $this->normalizeIdentifier($indexName);
756
        if ( ! $this->hasIndex($indexName)) {
757
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
758
        }
Benjamin Morel's avatar
Benjamin Morel committed
759

760 761 762 763
        return $this->_indexes[$indexName];
    }

    /**
764
     * @return Index[]
765 766 767 768 769 770 771
     */
    public function getIndexes()
    {
        return $this->_indexes;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
772
     * Returns the foreign key constraints.
773
     *
774
     * @return ForeignKeyConstraint[]
775
     */
776
    public function getForeignKeys()
777
    {
778
        return $this->_fkConstraints;
779 780
    }

Benjamin Morel's avatar
Benjamin Morel committed
781 782 783 784 785
    /**
     * @param string $name
     *
     * @return boolean
     */
786 787 788 789 790
    public function hasOption($name)
    {
        return isset($this->_options[$name]);
    }

Benjamin Morel's avatar
Benjamin Morel committed
791 792 793 794 795
    /**
     * @param string $name
     *
     * @return mixed
     */
796 797 798 799 800
    public function getOption($name)
    {
        return $this->_options[$name];
    }

Benjamin Morel's avatar
Benjamin Morel committed
801 802 803
    /**
     * @return array
     */
804 805 806 807 808
    public function getOptions()
    {
        return $this->_options;
    }

809
    /**
810
     * @param Visitor $visitor
Benjamin Morel's avatar
Benjamin Morel committed
811 812
     *
     * @return void
813 814 815 816 817
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptTable($this);

818
        foreach ($this->getColumns() as $column) {
819
            $visitor->acceptColumn($this, $column);
820 821
        }

822
        foreach ($this->getIndexes() as $index) {
823 824 825
            $visitor->acceptIndex($this, $index);
        }

826
        foreach ($this->getForeignKeys() as $constraint) {
827
            $visitor->acceptForeignKey($this, $constraint);
828 829
        }
    }
830 831

    /**
Benjamin Morel's avatar
Benjamin Morel committed
832 833 834
     * Clone of a Table triggers a deep clone of all affected assets.
     *
     * @return void
835 836 837
     */
    public function __clone()
    {
838
        foreach ($this->_columns as $k => $column) {
839 840
            $this->_columns[$k] = clone $column;
        }
841
        foreach ($this->_indexes as $k => $index) {
842 843
            $this->_indexes[$k] = clone $index;
        }
844
        foreach ($this->_fkConstraints as $k => $fk) {
845 846 847 848
            $this->_fkConstraints[$k] = clone $fk;
            $this->_fkConstraints[$k]->setLocalTable($this);
        }
    }
849 850 851 852 853 854 855 856 857 858 859 860 861 862

    /**
     * Normalizes a given identifier.
     *
     * Trims quotes and lowercases the given identifier.
     *
     * @param string $identifier The identifier to normalize.
     *
     * @return string The normalized identifier.
     */
    private function normalizeIdentifier($identifier)
    {
        return $this->trimQuotes(strtolower($identifier));
    }
863
}