5 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17 * This software consists of voluntary contributions made by many individuals
18 * and is licensed under the MIT license. For more information, see
19 * <http://www.doctrine-project.org>.
22 namespace Doctrine\DBAL\Schema;
24 use Doctrine\DBAL\Types\Type;
25 use Doctrine\DBAL\Schema\Visitor\Visitor;
26 use Doctrine\DBAL\DBALException;
29 * Object Representation of a table
32 * @link www.doctrine-project.org
35 * @author Benjamin Eberlei <kontakt@beberlei.de>
37 class Table extends AbstractAsset
42 protected $_name = null;
47 protected $_columns = array();
52 protected $_indexes = array();
57 protected $_primaryKeyName = false;
62 protected $_fkConstraints = array();
67 protected $_options = array();
72 protected $_schemaConfig = null;
76 * @param string $tableName
77 * @param array $columns
78 * @param array $indexes
79 * @param array $fkConstraints
80 * @param int $idGeneratorType
81 * @param array $options
83 public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array())
85 if (strlen($tableName) == 0) {
86 throw DBALException::invalidTableName($tableName);
89 $this->_setName($tableName);
90 $this->_idGeneratorType = $idGeneratorType;
92 foreach ($columns as $column) {
93 $this->_addColumn($column);
96 foreach ($indexes as $idx) {
97 $this->_addIndex($idx);
100 foreach ($fkConstraints as $constraint) {
101 $this->_addForeignKeyConstraint($constraint);
104 $this->_options = $options;
108 * @param SchemaConfig $schemaConfig
110 public function setSchemaConfig(SchemaConfig $schemaConfig)
112 $this->_schemaConfig = $schemaConfig;
118 protected function _getMaxIdentifierLength()
120 if ($this->_schemaConfig instanceof SchemaConfig) {
121 return $this->_schemaConfig->getMaxIdentifierLength();
130 * @param array $columns
131 * @param string $indexName
134 public function setPrimaryKey(array $columns, $indexName = false)
136 $primaryKey = $this->_createIndex($columns, $indexName ?: "primary", true, true);
138 foreach ($columns as $columnName) {
139 $column = $this->getColumn($columnName);
140 $column->setNotnull(true);
147 * @param array $columnNames
148 * @param string $indexName
151 public function addIndex(array $columnNames, $indexName = null)
153 if($indexName == null) {
154 $indexName = $this->_generateIdentifierName(
155 array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
159 return $this->_createIndex($columnNames, $indexName, false, false);
163 * Drop an index from this table.
165 * @param string $indexName
168 public function dropPrimaryKey()
170 $this->dropIndex($this->_primaryKeyName);
171 $this->_primaryKeyName = false;
175 * Drop an index from this table.
177 * @param string $indexName
180 public function dropIndex($indexName)
182 $indexName = strtolower($indexName);
183 if ( ! $this->hasIndex($indexName)) {
184 throw SchemaException::indexDoesNotExist($indexName, $this->_name);
186 unset($this->_indexes[$indexName]);
191 * @param array $columnNames
192 * @param string $indexName
195 public function addUniqueIndex(array $columnNames, $indexName = null)
197 if ($indexName === null) {
198 $indexName = $this->_generateIdentifierName(
199 array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
203 return $this->_createIndex($columnNames, $indexName, true, false);
207 * Check if an index begins in the order of the given columns.
209 * @param array $columnsNames
212 public function columnsAreIndexed(array $columnsNames)
214 foreach ($this->getIndexes() as $index) {
215 /* @var $index Index */
216 if ($index->spansColumns($columnsNames)) {
225 * @param array $columnNames
226 * @param string $indexName
227 * @param bool $isUnique
228 * @param bool $isPrimary
231 private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary)
233 if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
234 throw SchemaException::indexNameInvalid($indexName);
237 foreach ($columnNames as $columnName => $indexColOptions) {
238 if (is_numeric($columnName) && is_string($indexColOptions)) {
239 $columnName = $indexColOptions;
242 if ( ! $this->hasColumn($columnName)) {
243 throw SchemaException::columnDoesNotExist($columnName, $this->_name);
246 $this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary));
251 * @param string $columnName
252 * @param string $columnType
253 * @param array $options
256 public function addColumn($columnName, $typeName, array $options=array())
258 $column = new Column($columnName, Type::getType($typeName), $options);
260 $this->_addColumn($column);
267 * @param string $oldColumnName
268 * @param string $newColumnName
271 public function renameColumn($oldColumnName, $newColumnName)
273 throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " .
274 "the column instead. There is no fix available, because a schema diff cannot reliably detect if a " .
275 "column was renamed or one column was created and another one dropped.");
279 * Change Column Details
281 * @param string $columnName
282 * @param array $options
285 public function changeColumn($columnName, array $options)
287 $column = $this->getColumn($columnName);
288 $column->setOptions($options);
293 * Drop Column from Table
295 * @param string $columnName
298 public function dropColumn($columnName)
300 $columnName = strtolower($columnName);
301 $column = $this->getColumn($columnName);
302 unset($this->_columns[$columnName]);
308 * Add a foreign key constraint
310 * Name is inferred from the local columns
312 * @param Table $foreignTable
313 * @param array $localColumns
314 * @param array $foreignColumns
315 * @param array $options
316 * @param string $constraintName
319 public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null)
321 $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
322 return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
326 * Add a foreign key constraint
328 * Name is to be generated by the database itsself.
330 * @deprecated Use {@link addForeignKeyConstraint}
331 * @param Table $foreignTable
332 * @param array $localColumns
333 * @param array $foreignColumns
334 * @param array $options
337 public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
339 return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
343 * Add a foreign key constraint with a given name
345 * @deprecated Use {@link addForeignKeyConstraint}
346 * @param string $name
347 * @param Table $foreignTable
348 * @param array $localColumns
349 * @param array $foreignColumns
350 * @param array $options
353 public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
355 if ($foreignTable instanceof Table) {
356 $foreignTableName = $foreignTable->getName();
358 foreach ($foreignColumnNames as $columnName) {
359 if ( ! $foreignTable->hasColumn($columnName)) {
360 throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
364 $foreignTableName = $foreignTable;
367 foreach ($localColumnNames as $columnName) {
368 if ( ! $this->hasColumn($columnName)) {
369 throw SchemaException::columnDoesNotExist($columnName, $this->_name);
373 $constraint = new ForeignKeyConstraint(
374 $localColumnNames, $foreignTableName, $foreignColumnNames, $name, $options
376 $this->_addForeignKeyConstraint($constraint);
382 * @param string $name
383 * @param string $value
386 public function addOption($name, $value)
388 $this->_options[$name] = $value;
393 * @param Column $column
395 protected function _addColumn(Column $column)
397 $columnName = $column->getName();
398 $columnName = strtolower($columnName);
400 if (isset($this->_columns[$columnName])) {
401 throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
404 $this->_columns[$columnName] = $column;
410 * @param Index $indexCandidate
413 protected function _addIndex(Index $indexCandidate)
415 // check for duplicates
416 foreach ($this->_indexes as $existingIndex) {
417 if ($indexCandidate->isFullfilledBy($existingIndex)) {
422 $indexName = $indexCandidate->getName();
423 $indexName = strtolower($indexName);
425 if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) {
426 throw SchemaException::indexAlreadyExists($indexName, $this->_name);
429 // remove overruled indexes
430 foreach ($this->_indexes as $idxKey => $existingIndex) {
431 if ($indexCandidate->overrules($existingIndex)) {
432 unset($this->_indexes[$idxKey]);
436 if ($indexCandidate->isPrimary()) {
437 $this->_primaryKeyName = $indexName;
440 $this->_indexes[$indexName] = $indexCandidate;
445 * @param ForeignKeyConstraint $constraint
447 protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
449 $constraint->setLocalTable($this);
451 if(strlen($constraint->getName())) {
452 $name = $constraint->getName();
454 $name = $this->_generateIdentifierName(
455 array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
458 $name = strtolower($name);
460 $this->_fkConstraints[$name] = $constraint;
461 // add an explicit index on the foreign key columns. If there is already an index that fullfils this requirements drop the request.
462 // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
463 // lead to duplicates. This creates compuation overhead in this case, however no duplicate indexes are ever added (based on columns).
464 $this->addIndex($constraint->getColumns());
468 * Does Table have a foreign key constraint with the given name?
470 * @param string $constraintName
473 public function hasForeignKey($constraintName)
475 $constraintName = strtolower($constraintName);
476 return isset($this->_fkConstraints[$constraintName]);
480 * @param string $constraintName
481 * @return ForeignKeyConstraint
483 public function getForeignKey($constraintName)
485 $constraintName = strtolower($constraintName);
486 if(!$this->hasForeignKey($constraintName)) {
487 throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
490 return $this->_fkConstraints[$constraintName];
493 public function removeForeignKey($constraintName)
495 $constraintName = strtolower($constraintName);
496 if(!$this->hasForeignKey($constraintName)) {
497 throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
500 unset($this->_fkConstraints[$constraintName]);
506 public function getColumns()
508 $columns = $this->_columns;
513 if ($this->hasPrimaryKey()) {
514 $pkCols = $this->getPrimaryKey()->getColumns();
516 foreach ($this->getForeignKeys() as $fk) {
517 /* @var $fk ForeignKeyConstraint */
518 $fkCols = array_merge($fkCols, $fk->getColumns());
520 $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));
522 uksort($columns, function($a, $b) use($colNames) {
523 return (array_search($a, $colNames) >= array_search($b, $colNames));
530 * Does this table have a column with the given name?
532 * @param string $columnName
535 public function hasColumn($columnName)
537 $columnName = $this->trimQuotes(strtolower($columnName));
538 return isset($this->_columns[$columnName]);
542 * Get a column instance
544 * @param string $columnName
547 public function getColumn($columnName)
549 $columnName = strtolower($this->trimQuotes($columnName));
550 if ( ! $this->hasColumn($columnName)) {
551 throw SchemaException::columnDoesNotExist($columnName, $this->_name);
554 return $this->_columns[$columnName];
560 public function getPrimaryKey()
562 if ( ! $this->hasPrimaryKey()) {
565 return $this->getIndex($this->_primaryKeyName);
568 public function getPrimaryKeyColumns()
570 if ( ! $this->hasPrimaryKey()) {
571 throw new DBALException("Table " . $this->getName() . " has no primary key.");
573 return $this->getPrimaryKey()->getColumns();
577 * Check if this table has a primary key.
581 public function hasPrimaryKey()
583 return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
587 * @param string $indexName
590 public function hasIndex($indexName)
592 $indexName = strtolower($indexName);
593 return (isset($this->_indexes[$indexName]));
597 * @param string $indexName
600 public function getIndex($indexName)
602 $indexName = strtolower($indexName);
603 if ( ! $this->hasIndex($indexName)) {
604 throw SchemaException::indexDoesNotExist($indexName, $this->_name);
606 return $this->_indexes[$indexName];
612 public function getIndexes()
614 return $this->_indexes;
622 public function getForeignKeys()
624 return $this->_fkConstraints;
627 public function hasOption($name)
629 return isset($this->_options[$name]);
632 public function getOption($name)
634 return $this->_options[$name];
637 public function getOptions()
639 return $this->_options;
643 * @param Visitor $visitor
645 public function visit(Visitor $visitor)
647 $visitor->acceptTable($this);
649 foreach ($this->getColumns() as $column) {
650 $visitor->acceptColumn($this, $column);
653 foreach ($this->getIndexes() as $index) {
654 $visitor->acceptIndex($this, $index);
657 foreach ($this->getForeignKeys() as $constraint) {
658 $visitor->acceptForeignKey($this, $constraint);
663 * Clone of a Table triggers a deep clone of all affected assets
665 public function __clone()
667 foreach ($this->_columns as $k => $column) {
668 $this->_columns[$k] = clone $column;
670 foreach ($this->_indexes as $k => $index) {
671 $this->_indexes[$k] = clone $index;
673 foreach ($this->_fkConstraints as $k => $fk) {
674 $this->_fkConstraints[$k] = clone $fk;
675 $this->_fkConstraints[$k]->setLocalTable($this);