Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / dbal / lib / Doctrine / DBAL / Schema / Table.php
1 <?php
2 /*
3  *  $Id$
4  *
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.
16  *
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>.
20  */
21
22 namespace Doctrine\DBAL\Schema;
23
24 use Doctrine\DBAL\Types\Type;
25 use Doctrine\DBAL\Schema\Visitor\Visitor;
26 use Doctrine\DBAL\DBALException;
27
28 /**
29  * Object Representation of a table
30  *
31  * 
32  * @link    www.doctrine-project.org
33  * @since   2.0
34  * @version $Revision$
35  * @author  Benjamin Eberlei <kontakt@beberlei.de>
36  */
37 class Table extends AbstractAsset
38 {
39     /**
40      * @var string
41      */
42     protected $_name = null;
43
44     /**
45      * @var array
46      */
47     protected $_columns = array();
48
49     /**
50      * @var array
51      */
52     protected $_indexes = array();
53
54     /**
55      * @var string
56      */
57     protected $_primaryKeyName = false;
58
59     /**
60      * @var array
61      */
62     protected $_fkConstraints = array();
63
64     /**
65      * @var array
66      */
67     protected $_options = array();
68
69     /**
70      * @var SchemaConfig
71      */
72     protected $_schemaConfig = null;
73
74     /**
75      *
76      * @param string $tableName
77      * @param array $columns
78      * @param array $indexes
79      * @param array $fkConstraints
80      * @param int $idGeneratorType
81      * @param array $options
82      */
83     public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array())
84     {
85         if (strlen($tableName) == 0) {
86             throw DBALException::invalidTableName($tableName);
87         }
88
89         $this->_setName($tableName);
90         $this->_idGeneratorType = $idGeneratorType;
91
92         foreach ($columns as $column) {
93             $this->_addColumn($column);
94         }
95
96         foreach ($indexes as $idx) {
97             $this->_addIndex($idx);
98         }
99
100         foreach ($fkConstraints as $constraint) {
101             $this->_addForeignKeyConstraint($constraint);
102         }
103
104         $this->_options = $options;
105     }
106
107     /**
108      * @param SchemaConfig $schemaConfig
109      */
110     public function setSchemaConfig(SchemaConfig $schemaConfig)
111     {
112         $this->_schemaConfig = $schemaConfig;
113     }
114
115     /**
116      * @return int
117      */
118     protected function _getMaxIdentifierLength()
119     {
120         if ($this->_schemaConfig instanceof SchemaConfig) {
121             return $this->_schemaConfig->getMaxIdentifierLength();
122         } else {
123             return 63;
124         }
125     }
126
127     /**
128      * Set Primary Key
129      *
130      * @param array $columns
131      * @param string $indexName
132      * @return Table
133      */
134     public function setPrimaryKey(array $columns, $indexName = false)
135     {
136         $primaryKey = $this->_createIndex($columns, $indexName ?: "primary", true, true);
137
138         foreach ($columns as $columnName) {
139             $column = $this->getColumn($columnName);
140             $column->setNotnull(true);
141         }
142
143         return $primaryKey;
144     }
145
146     /**
147      * @param array $columnNames
148      * @param string $indexName
149      * @return Table
150      */
151     public function addIndex(array $columnNames, $indexName = null)
152     {
153         if($indexName == null) {
154             $indexName = $this->_generateIdentifierName(
155                 array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
156             );
157         }
158
159         return $this->_createIndex($columnNames, $indexName, false, false);
160     }
161
162     /**
163      * Drop an index from this table.
164      *
165      * @param string $indexName
166      * @return void
167      */
168     public function dropPrimaryKey()
169     {
170         $this->dropIndex($this->_primaryKeyName);
171         $this->_primaryKeyName = false;
172     }
173
174     /**
175      * Drop an index from this table.
176      *
177      * @param string $indexName
178      * @return void
179      */
180     public function dropIndex($indexName)
181     {
182         $indexName = strtolower($indexName);
183         if ( ! $this->hasIndex($indexName)) {
184             throw SchemaException::indexDoesNotExist($indexName, $this->_name);
185         }
186         unset($this->_indexes[$indexName]);
187     }
188
189     /**
190      *
191      * @param array $columnNames
192      * @param string $indexName
193      * @return Table
194      */
195     public function addUniqueIndex(array $columnNames, $indexName = null)
196     {
197         if ($indexName === null) {
198             $indexName = $this->_generateIdentifierName(
199                 array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
200             );
201         }
202
203         return $this->_createIndex($columnNames, $indexName, true, false);
204     }
205
206     /**
207      * Check if an index begins in the order of the given columns.
208      *
209      * @param  array $columnsNames
210      * @return bool
211      */
212     public function columnsAreIndexed(array $columnsNames)
213     {
214         foreach ($this->getIndexes() as $index) {
215             /* @var $index Index */
216             if ($index->spansColumns($columnsNames)) {
217                 return true;
218             }
219         }
220         return false;
221     }
222
223     /**
224      *
225      * @param array $columnNames
226      * @param string $indexName
227      * @param bool $isUnique
228      * @param bool $isPrimary
229      * @return Table
230      */
231     private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary)
232     {
233         if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
234             throw SchemaException::indexNameInvalid($indexName);
235         }
236
237         foreach ($columnNames as $columnName => $indexColOptions) {
238             if (is_numeric($columnName) && is_string($indexColOptions)) {
239                 $columnName = $indexColOptions;
240             }
241
242             if ( ! $this->hasColumn($columnName)) {
243                 throw SchemaException::columnDoesNotExist($columnName, $this->_name);
244             }
245         }
246         $this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary));
247         return $this;
248     }
249
250     /**
251      * @param string $columnName
252      * @param string $columnType
253      * @param array $options
254      * @return Column
255      */
256     public function addColumn($columnName, $typeName, array $options=array())
257     {
258         $column = new Column($columnName, Type::getType($typeName), $options);
259
260         $this->_addColumn($column);
261         return $column;
262     }
263
264     /**
265      * Rename Column
266      *
267      * @param string $oldColumnName
268      * @param string $newColumnName
269      * @return Table
270      */
271     public function renameColumn($oldColumnName, $newColumnName)
272     {
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.");
276     }
277
278     /**
279      * Change Column Details
280      *
281      * @param string $columnName
282      * @param array $options
283      * @return Table
284      */
285     public function changeColumn($columnName, array $options)
286     {
287         $column = $this->getColumn($columnName);
288         $column->setOptions($options);
289         return $this;
290     }
291
292     /**
293      * Drop Column from Table
294      *
295      * @param string $columnName
296      * @return Table
297      */
298     public function dropColumn($columnName)
299     {
300         $columnName = strtolower($columnName);
301         $column = $this->getColumn($columnName);
302         unset($this->_columns[$columnName]);
303         return $this;
304     }
305
306
307     /**
308      * Add a foreign key constraint
309      *
310      * Name is inferred from the local columns
311      *
312      * @param Table $foreignTable
313      * @param array $localColumns
314      * @param array $foreignColumns
315      * @param array $options
316      * @param string $constraintName
317      * @return Table
318      */
319     public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null)
320     {
321         $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
322         return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
323     }
324
325     /**
326      * Add a foreign key constraint
327      *
328      * Name is to be generated by the database itsself.
329      *
330      * @deprecated Use {@link addForeignKeyConstraint}
331      * @param Table $foreignTable
332      * @param array $localColumns
333      * @param array $foreignColumns
334      * @param array $options
335      * @return Table
336      */
337     public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
338     {
339         return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
340     }
341
342     /**
343      * Add a foreign key constraint with a given name
344      *
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
351      * @return Table
352      */
353     public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
354     {
355         if ($foreignTable instanceof Table) {
356             $foreignTableName = $foreignTable->getName();
357
358             foreach ($foreignColumnNames as $columnName) {
359                 if ( ! $foreignTable->hasColumn($columnName)) {
360                     throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
361                 }
362             }
363         } else {
364             $foreignTableName = $foreignTable;
365         }
366
367         foreach ($localColumnNames as $columnName) {
368             if ( ! $this->hasColumn($columnName)) {
369                 throw SchemaException::columnDoesNotExist($columnName, $this->_name);
370             }
371         }
372
373         $constraint = new ForeignKeyConstraint(
374             $localColumnNames, $foreignTableName, $foreignColumnNames, $name, $options
375         );
376         $this->_addForeignKeyConstraint($constraint);
377
378         return $this;
379     }
380
381     /**
382      * @param string $name
383      * @param string $value
384      * @return Table
385      */
386     public function addOption($name, $value)
387     {
388         $this->_options[$name] = $value;
389         return $this;
390     }
391
392     /**
393      * @param Column $column
394      */
395     protected function _addColumn(Column $column)
396     {
397         $columnName = $column->getName();
398         $columnName = strtolower($columnName);
399
400         if (isset($this->_columns[$columnName])) {
401             throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
402         }
403
404         $this->_columns[$columnName] = $column;
405     }
406
407     /**
408      * Add index to table
409      *
410      * @param Index $indexCandidate
411      * @return Table
412      */
413     protected function _addIndex(Index $indexCandidate)
414     {
415         // check for duplicates
416         foreach ($this->_indexes as $existingIndex) {
417             if ($indexCandidate->isFullfilledBy($existingIndex)) {
418                 return $this;
419             }
420         }
421
422         $indexName = $indexCandidate->getName();
423         $indexName = strtolower($indexName);
424
425         if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) {
426             throw SchemaException::indexAlreadyExists($indexName, $this->_name);
427         }
428
429         // remove overruled indexes
430         foreach ($this->_indexes as $idxKey => $existingIndex) {
431             if ($indexCandidate->overrules($existingIndex)) {
432                 unset($this->_indexes[$idxKey]);
433             }
434         }
435
436         if ($indexCandidate->isPrimary()) {
437             $this->_primaryKeyName = $indexName;
438         }
439
440         $this->_indexes[$indexName] = $indexCandidate;
441         return $this;
442     }
443
444     /**
445      * @param ForeignKeyConstraint $constraint
446      */
447     protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
448     {
449         $constraint->setLocalTable($this);
450
451         if(strlen($constraint->getName())) {
452             $name = $constraint->getName();
453         } else {
454             $name = $this->_generateIdentifierName(
455                 array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
456             );
457         }
458         $name = strtolower($name);
459
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());
465     }
466
467     /**
468      * Does Table have a foreign key constraint with the given name?
469      *      *
470      * @param  string $constraintName
471      * @return bool
472      */
473     public function hasForeignKey($constraintName)
474     {
475         $constraintName = strtolower($constraintName);
476         return isset($this->_fkConstraints[$constraintName]);
477     }
478
479     /**
480      * @param string $constraintName
481      * @return ForeignKeyConstraint
482      */
483     public function getForeignKey($constraintName)
484     {
485         $constraintName = strtolower($constraintName);
486         if(!$this->hasForeignKey($constraintName)) {
487             throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
488         }
489
490         return $this->_fkConstraints[$constraintName];
491     }
492
493     public function removeForeignKey($constraintName)
494     {
495         $constraintName = strtolower($constraintName);
496         if(!$this->hasForeignKey($constraintName)) {
497             throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
498         }
499
500         unset($this->_fkConstraints[$constraintName]);
501     }
502
503     /**
504      * @return Column[]
505      */
506     public function getColumns()
507     {
508         $columns = $this->_columns;
509
510         $pkCols = array();
511         $fkCols = array();
512
513         if ($this->hasPrimaryKey()) {
514             $pkCols = $this->getPrimaryKey()->getColumns();
515         }
516         foreach ($this->getForeignKeys() as $fk) {
517             /* @var $fk ForeignKeyConstraint */
518             $fkCols = array_merge($fkCols, $fk->getColumns());
519         }
520         $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));
521
522         uksort($columns, function($a, $b) use($colNames) {
523             return (array_search($a, $colNames) >= array_search($b, $colNames));
524         });
525         return $columns;
526     }
527
528
529     /**
530      * Does this table have a column with the given name?
531      *
532      * @param  string $columnName
533      * @return bool
534      */
535     public function hasColumn($columnName)
536     {
537         $columnName = $this->trimQuotes(strtolower($columnName));
538         return isset($this->_columns[$columnName]);
539     }
540
541     /**
542      * Get a column instance
543      *
544      * @param  string $columnName
545      * @return Column
546      */
547     public function getColumn($columnName)
548     {
549         $columnName = strtolower($this->trimQuotes($columnName));
550         if ( ! $this->hasColumn($columnName)) {
551             throw SchemaException::columnDoesNotExist($columnName, $this->_name);
552         }
553
554         return $this->_columns[$columnName];
555     }
556
557     /**
558      * @return Index|null
559      */
560     public function getPrimaryKey()
561     {
562         if ( ! $this->hasPrimaryKey()) {
563             return null;
564         }
565         return $this->getIndex($this->_primaryKeyName);
566     }
567
568     public function getPrimaryKeyColumns()
569     {
570         if ( ! $this->hasPrimaryKey()) {
571             throw new DBALException("Table " . $this->getName() . " has no primary key.");
572         }
573         return $this->getPrimaryKey()->getColumns();
574     }
575
576     /**
577      * Check if this table has a primary key.
578      *
579      * @return bool
580      */
581     public function hasPrimaryKey()
582     {
583         return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
584     }
585
586     /**
587      * @param  string $indexName
588      * @return bool
589      */
590     public function hasIndex($indexName)
591     {
592         $indexName = strtolower($indexName);
593         return (isset($this->_indexes[$indexName]));
594     }
595
596     /**
597      * @param  string $indexName
598      * @return Index
599      */
600     public function getIndex($indexName)
601     {
602         $indexName = strtolower($indexName);
603         if ( ! $this->hasIndex($indexName)) {
604             throw SchemaException::indexDoesNotExist($indexName, $this->_name);
605         }
606         return $this->_indexes[$indexName];
607     }
608
609     /**
610      * @return array
611      */
612     public function getIndexes()
613     {
614         return $this->_indexes;
615     }
616
617     /**
618      * Get Constraints
619      *
620      * @return array
621      */
622     public function getForeignKeys()
623     {
624         return $this->_fkConstraints;
625     }
626
627     public function hasOption($name)
628     {
629         return isset($this->_options[$name]);
630     }
631
632     public function getOption($name)
633     {
634         return $this->_options[$name];
635     }
636
637     public function getOptions()
638     {
639         return $this->_options;
640     }
641
642     /**
643      * @param Visitor $visitor
644      */
645     public function visit(Visitor $visitor)
646     {
647         $visitor->acceptTable($this);
648
649         foreach ($this->getColumns() as $column) {
650             $visitor->acceptColumn($this, $column);
651         }
652
653         foreach ($this->getIndexes() as $index) {
654             $visitor->acceptIndex($this, $index);
655         }
656
657         foreach ($this->getForeignKeys() as $constraint) {
658             $visitor->acceptForeignKey($this, $constraint);
659         }
660     }
661
662     /**
663      * Clone of a Table triggers a deep clone of all affected assets
664      */
665     public function __clone()
666     {
667         foreach ($this->_columns as $k => $column) {
668             $this->_columns[$k] = clone $column;
669         }
670         foreach ($this->_indexes as $k => $index) {
671             $this->_indexes[$k] = clone $index;
672         }
673         foreach ($this->_fkConstraints as $k => $fk) {
674             $this->_fkConstraints[$k] = clone $fk;
675             $this->_fkConstraints[$k]->setLocalTable($this);
676         }
677     }
678 }