Table.php 22.2 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 45
     */
    protected $_columns = array();

    /**
46
     * @var Index[]
47 48 49 50 51 52 53 54 55
     */
    protected $_indexes = array();

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

    /**
56
     * @var ForeignKeyConstraint[]
57
     */
58
    protected $_fkConstraints = array();
59 60 61 62 63 64

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

65
    /**
66
     * @var SchemaConfig
67 68 69
     */
    protected $_schemaConfig = null;

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

86
        $this->_setName($tableName);
87

88
        foreach ($columns as $column) {
89 90
            $this->_addColumn($column);
        }
91

92
        foreach ($indexes as $idx) {
93 94 95
            $this->_addIndex($idx);
        }

96
        foreach ($fkConstraints as $constraint) {
97
            $this->_addForeignKeyConstraint($constraint);
98 99 100 101 102
        }

        $this->_options = $options;
    }

103
    /**
104
     * @param SchemaConfig $schemaConfig
Benjamin Morel's avatar
Benjamin Morel committed
105 106
     *
     * @return void
107 108 109 110 111 112 113
     */
    public function setSchemaConfig(SchemaConfig $schemaConfig)
    {
        $this->_schemaConfig = $schemaConfig;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
114
     * @return integer
115 116 117 118 119 120 121 122 123 124
     */
    protected function _getMaxIdentifierLength()
    {
        if ($this->_schemaConfig instanceof SchemaConfig) {
            return $this->_schemaConfig->getMaxIdentifierLength();
        } else {
            return 63;
        }
    }

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

137
        foreach ($columns as $columnName) {
138 139 140 141
            $column = $this->getColumn($columnName);
            $column->setNotnull(true);
        }

142
        return $this;
143 144 145
    }

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

160
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags));
161 162
    }

163
    /**
Benjamin Morel's avatar
Benjamin Morel committed
164
     * Drops the primary key from this table.
165 166 167 168 169 170
     *
     * @return void
     */
    public function dropPrimaryKey()
    {
        $this->dropIndex($this->_primaryKeyName);
171
        $this->_primaryKeyName = false;
172 173 174
    }

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

192
    /**
Benjamin Morel's avatar
Benjamin Morel committed
193 194
     * @param array       $columnNames
     * @param string|null $indexName
195
     *
196
     * @return self
197
     */
198
    public function addUniqueIndex(array $columnNames, $indexName = null)
199
    {
200
        if ($indexName === null) {
201
            $indexName = $this->_generateIdentifierName(
202
                array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
203
            );
204 205
        }

206
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false));
207 208
    }

209 210 211 212 213 214 215
    /**
     * 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.
     *
216
     * @return self This table instance.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
     *
     * @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)
    {
        $oldIndexName           = strtolower($oldIndexName);
        $normalizedNewIndexName = strtolower($newIndexName);

        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()) {
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName);
        }

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

255
    /**
Benjamin Morel's avatar
Benjamin Morel committed
256 257 258
     * Checks if an index begins in the order of the given columns.
     *
     * @param array $columnsNames
259
     *
Benjamin Morel's avatar
Benjamin Morel committed
260
     * @return boolean
261 262 263
     */
    public function columnsAreIndexed(array $columnsNames)
    {
264
        foreach ($this->getIndexes() as $index) {
265 266
            /* @var $index Index */
            if ($index->spansColumns($columnsNames)) {
267 268 269
                return true;
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
270

271 272 273
        return false;
    }

274
    /**
Benjamin Morel's avatar
Benjamin Morel committed
275 276 277 278 279
     * @param array   $columnNames
     * @param string  $indexName
     * @param boolean $isUnique
     * @param boolean $isPrimary
     * @param array   $flags
280
     *
281
     * @return Index
Benjamin Morel's avatar
Benjamin Morel committed
282
     *
283
     * @throws SchemaException
284
     */
285
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array())
286 287 288 289 290
    {
        if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
            throw SchemaException::indexNameInvalid($indexName);
        }

291
        foreach ($columnNames as $columnName => $indexColOptions) {
beberlei's avatar
beberlei committed
292 293 294 295
            if (is_numeric($columnName) && is_string($indexColOptions)) {
                $columnName = $indexColOptions;
            }

296
            if ( ! $this->hasColumn($columnName)) {
297
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
298 299
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
300

301
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags);
302 303 304 305
    }

    /**
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
306 307 308
     * @param string $typeName
     * @param array  $options
     *
309
     * @return Column
310
     */
311
    public function addColumn($columnName, $typeName, array $options=array())
312 313 314 315
    {
        $column = new Column($columnName, Type::getType($typeName), $options);

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

317
        return $column;
318 319 320
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
321
     * Renames a Column.
322 323 324
     *
     * @param string $oldColumnName
     * @param string $newColumnName
Benjamin Morel's avatar
Benjamin Morel committed
325
     *
326
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
327
     *
328
     * @throws DBALException
329 330 331
     */
    public function renameColumn($oldColumnName, $newColumnName)
    {
332 333 334
        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.");
335 336 337
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
338
     * Change Column Details.
339
     *
340
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
341 342
     * @param array  $options
     *
343
     * @return self
344 345 346 347 348
     */
    public function changeColumn($columnName, array $options)
    {
        $column = $this->getColumn($columnName);
        $column->setOptions($options);
Benjamin Morel's avatar
Benjamin Morel committed
349

350 351 352 353
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
354
     * Drops a Column from the Table.
355
     *
356
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
357
     *
358
     * @return self
359 360 361
     */
    public function dropColumn($columnName)
    {
362
        $columnName = strtolower($columnName);
363
        unset($this->_columns[$columnName]);
Benjamin Morel's avatar
Benjamin Morel committed
364

365 366 367 368
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
369
     * Adds a foreign key constraint.
370
     *
Benjamin Morel's avatar
Benjamin Morel committed
371
     * Name is inferred from the local columns.
372
     *
373 374 375 376 377
     * @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
378
     *
379
     * @return self
380
     */
381
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null)
382
    {
383
        $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
Benjamin Morel's avatar
Benjamin Morel committed
384

385
        return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
386 387
    }

388
    /**
Benjamin Morel's avatar
Benjamin Morel committed
389
     * Adds a foreign key constraint.
390
     *
Pascal Borreli's avatar
Pascal Borreli committed
391
     * Name is to be generated by the database itself.
392
     *
393
     * @deprecated Use {@link addForeignKeyConstraint}
Benjamin Morel's avatar
Benjamin Morel committed
394
     *
395 396 397 398
     * @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
399
     *
400
     * @return self
401 402 403
     */
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
    {
404
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
405 406
    }

407
    /**
Benjamin Morel's avatar
Benjamin Morel committed
408
     * Adds a foreign key constraint with a given name.
409
     *
410
     * @deprecated Use {@link addForeignKeyConstraint}
Benjamin Morel's avatar
Benjamin Morel committed
411
     *
412 413 414 415 416
     * @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
417
     *
418
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
419
     *
420
     * @throws SchemaException
421 422
     */
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
423
    {
424
        if ($foreignTable instanceof Table) {
425
            foreach ($foreignColumnNames as $columnName) {
426
                if ( ! $foreignTable->hasColumn($columnName)) {
427
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
428
                }
429 430
            }
        }
431

432
        foreach ($localColumnNames as $columnName) {
433
            if ( ! $this->hasColumn($columnName)) {
434
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
435 436
            }
        }
437

438
        $constraint = new ForeignKeyConstraint(
439
            $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options
440
        );
441
        $this->_addForeignKeyConstraint($constraint);
442

443 444 445 446 447 448
        return $this;
    }

    /**
     * @param string $name
     * @param string $value
Benjamin Morel's avatar
Benjamin Morel committed
449
     *
450
     * @return self
451 452 453 454
     */
    public function addOption($name, $value)
    {
        $this->_options[$name] = $value;
Benjamin Morel's avatar
Benjamin Morel committed
455

456 457 458 459
        return $this;
    }

    /**
460
     * @param Column $column
Benjamin Morel's avatar
Benjamin Morel committed
461 462 463
     *
     * @return void
     *
464
     * @throws SchemaException
465 466 467
     */
    protected function _addColumn(Column $column)
    {
468 469 470
        $columnName = $column->getName();
        $columnName = strtolower($columnName);

471
        if (isset($this->_columns[$columnName])) {
472
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
473 474 475 476 477 478
        }

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

    /**
Benjamin Morel's avatar
Benjamin Morel committed
479
     * Adds an index to the table.
480
     *
481
     * @param Index $indexCandidate
Benjamin Morel's avatar
Benjamin Morel committed
482
     *
483
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
484
     *
485
     * @throws SchemaException
486
     */
487
    protected function _addIndex(Index $indexCandidate)
488
    {
489
        // check for duplicates
490
        foreach ($this->_indexes as $existingIndex) {
491
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
492 493 494 495
                return $this;
            }
        }

496
        $indexName = $indexCandidate->getName();
497
        $indexName = strtolower($indexName);
498

499
        if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) {
500
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
501 502
        }

503
        // remove overruled indexes
504
        foreach ($this->_indexes as $idxKey => $existingIndex) {
505 506 507 508 509 510
            if ($indexCandidate->overrules($existingIndex)) {
                unset($this->_indexes[$idxKey]);
            }
        }

        if ($indexCandidate->isPrimary()) {
511 512 513
            $this->_primaryKeyName = $indexName;
        }

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

516 517 518 519
        return $this;
    }

    /**
520
     * @param ForeignKeyConstraint $constraint
Benjamin Morel's avatar
Benjamin Morel committed
521 522
     *
     * @return void
523
     */
524
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
525
    {
526
        $constraint->setLocalTable($this);
527

Steve Müller's avatar
Steve Müller committed
528
        if (strlen($constraint->getName())) {
529 530 531
            $name = $constraint->getName();
        } else {
            $name = $this->_generateIdentifierName(
532
                array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
533 534
            );
        }
535
        $name = strtolower($name);
536 537

        $this->_fkConstraints[$name] = $constraint;
Pascal Borreli's avatar
Pascal Borreli committed
538
        // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
539
        // 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
540
        // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
541
        $this->addIndex($constraint->getColumns());
542 543 544
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
545 546 547 548 549
     * Returns whether this table has a foreign key constraint with the given name.
     *
     * @param string $constraintName
     *
     * @return boolean
550 551 552
     */
    public function hasForeignKey($constraintName)
    {
553
        $constraintName = strtolower($constraintName);
Benjamin Morel's avatar
Benjamin Morel committed
554

555 556 557 558
        return isset($this->_fkConstraints[$constraintName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
559 560 561 562
     * Returns the foreign key constraint with the given name.
     *
     * @param string $constraintName The constraint name.
     *
563
     * @return ForeignKeyConstraint
Benjamin Morel's avatar
Benjamin Morel committed
564
     *
565
     * @throws SchemaException If the foreign key does not exist.
566 567 568
     */
    public function getForeignKey($constraintName)
    {
569
        $constraintName = strtolower($constraintName);
Steve Müller's avatar
Steve Müller committed
570
        if (!$this->hasForeignKey($constraintName)) {
571
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
572 573 574
        }

        return $this->_fkConstraints[$constraintName];
575 576
    }

Benjamin Morel's avatar
Benjamin Morel committed
577 578 579 580 581 582 583
    /**
     * Removes the foreign key constraint with the given name.
     *
     * @param string $constraintName The constraint name.
     *
     * @return void
     *
584
     * @throws SchemaException
Benjamin Morel's avatar
Benjamin Morel committed
585
     */
586 587 588
    public function removeForeignKey($constraintName)
    {
        $constraintName = strtolower($constraintName);
Steve Müller's avatar
Steve Müller committed
589
        if (!$this->hasForeignKey($constraintName)) {
590 591 592 593 594 595
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
        }

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

596
    /**
597
     * @return Column[]
598 599 600
     */
    public function getColumns()
    {
601 602 603 604 605
        $columns = $this->_columns;

        $pkCols = array();
        $fkCols = array();

606
        if ($this->hasPrimaryKey()) {
607 608
            $pkCols = $this->getPrimaryKey()->getColumns();
        }
609
        foreach ($this->getForeignKeys() as $fk) {
610 611 612 613 614
            /* @var $fk ForeignKeyConstraint */
            $fkCols = array_merge($fkCols, $fk->getColumns());
        }
        $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));

Steve Müller's avatar
Steve Müller committed
615
        uksort($columns, function ($a, $b) use ($colNames) {
616 617
            return (array_search($a, $colNames) >= array_search($b, $colNames));
        });
Benjamin Morel's avatar
Benjamin Morel committed
618

619
        return $columns;
620 621 622
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
623
     * Returns whether this table has a Column with the given name.
624
     *
Benjamin Morel's avatar
Benjamin Morel committed
625 626 627
     * @param string $columnName The column name.
     *
     * @return boolean
628 629 630
     */
    public function hasColumn($columnName)
    {
631
        $columnName = $this->trimQuotes(strtolower($columnName));
Benjamin Morel's avatar
Benjamin Morel committed
632

633 634 635 636
        return isset($this->_columns[$columnName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
637 638 639
     * Returns the Column with the given name.
     *
     * @param string $columnName The column name.
640
     *
641
     * @return Column
Benjamin Morel's avatar
Benjamin Morel committed
642
     *
643
     * @throws SchemaException If the column does not exist.
644 645 646
     */
    public function getColumn($columnName)
    {
647
        $columnName = strtolower($this->trimQuotes($columnName));
648
        if ( ! $this->hasColumn($columnName)) {
649
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
650 651 652 653 654 655
        }

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

    /**
Benjamin Morel's avatar
Benjamin Morel committed
656 657
     * Returns the primary key.
     *
658
     * @return Index|null The primary key, or null if this Table has no primary key.
659 660 661
     */
    public function getPrimaryKey()
    {
662
        if ( ! $this->hasPrimaryKey()) {
663 664
            return null;
        }
Benjamin Morel's avatar
Benjamin Morel committed
665

666 667 668
        return $this->getIndex($this->_primaryKeyName);
    }

Benjamin Morel's avatar
Benjamin Morel committed
669 670 671 672 673
    /**
     * Returns the primary key columns.
     *
     * @return array
     *
674
     * @throws DBALException
Benjamin Morel's avatar
Benjamin Morel committed
675
     */
676 677 678 679 680
    public function getPrimaryKeyColumns()
    {
        if ( ! $this->hasPrimaryKey()) {
            throw new DBALException("Table " . $this->getName() . " has no primary key.");
        }
Benjamin Morel's avatar
Benjamin Morel committed
681

682 683 684
        return $this->getPrimaryKey()->getColumns();
    }

685
    /**
Benjamin Morel's avatar
Benjamin Morel committed
686
     * Returns whether this table has a primary key.
687
     *
Benjamin Morel's avatar
Benjamin Morel committed
688
     * @return boolean
689 690 691 692 693 694
     */
    public function hasPrimaryKey()
    {
        return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
    }

695
    /**
Benjamin Morel's avatar
Benjamin Morel committed
696 697 698 699 700
     * Returns whether this table has an Index with the given name.
     *
     * @param string $indexName The index name.
     *
     * @return boolean
701 702 703
     */
    public function hasIndex($indexName)
    {
704
        $indexName = strtolower($indexName);
Benjamin Morel's avatar
Benjamin Morel committed
705

706 707 708 709
        return (isset($this->_indexes[$indexName]));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
710 711 712 713
     * Returns the Index with the given name.
     *
     * @param string $indexName The index name.
     *
714
     * @return Index
Benjamin Morel's avatar
Benjamin Morel committed
715
     *
716
     * @throws SchemaException If the index does not exist.
717 718 719
     */
    public function getIndex($indexName)
    {
720
        $indexName = strtolower($indexName);
721
        if ( ! $this->hasIndex($indexName)) {
722
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
723
        }
Benjamin Morel's avatar
Benjamin Morel committed
724

725 726 727 728
        return $this->_indexes[$indexName];
    }

    /**
729
     * @return Index[]
730 731 732 733 734 735 736
     */
    public function getIndexes()
    {
        return $this->_indexes;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
737
     * Returns the foreign key constraints.
738
     *
739
     * @return ForeignKeyConstraint[]
740
     */
741
    public function getForeignKeys()
742
    {
743
        return $this->_fkConstraints;
744 745
    }

Benjamin Morel's avatar
Benjamin Morel committed
746 747 748 749 750
    /**
     * @param string $name
     *
     * @return boolean
     */
751 752 753 754 755
    public function hasOption($name)
    {
        return isset($this->_options[$name]);
    }

Benjamin Morel's avatar
Benjamin Morel committed
756 757 758 759 760
    /**
     * @param string $name
     *
     * @return mixed
     */
761 762 763 764 765
    public function getOption($name)
    {
        return $this->_options[$name];
    }

Benjamin Morel's avatar
Benjamin Morel committed
766 767 768
    /**
     * @return array
     */
769 770 771 772 773
    public function getOptions()
    {
        return $this->_options;
    }

774
    /**
775
     * @param Visitor $visitor
Benjamin Morel's avatar
Benjamin Morel committed
776 777
     *
     * @return void
778 779 780 781 782
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptTable($this);

783
        foreach ($this->getColumns() as $column) {
784
            $visitor->acceptColumn($this, $column);
785 786
        }

787
        foreach ($this->getIndexes() as $index) {
788 789 790
            $visitor->acceptIndex($this, $index);
        }

791
        foreach ($this->getForeignKeys() as $constraint) {
792
            $visitor->acceptForeignKey($this, $constraint);
793 794
        }
    }
795 796

    /**
Benjamin Morel's avatar
Benjamin Morel committed
797 798 799
     * Clone of a Table triggers a deep clone of all affected assets.
     *
     * @return void
800 801 802
     */
    public function __clone()
    {
803
        foreach ($this->_columns as $k => $column) {
804 805
            $this->_columns[$k] = clone $column;
        }
806
        foreach ($this->_indexes as $k => $index) {
807 808
            $this->_indexes[$k] = clone $index;
        }
809
        foreach ($this->_fkConstraints as $k => $fk) {
810 811 812 813
            $this->_fkConstraints[$k] = clone $fk;
            $this->_fkConstraints[$k]->setLocalTable($this);
        }
    }
814
}