|
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 LGPL. 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 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL |
|
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 * |
|
164 * @param array $columnNames |
|
165 * @param string $indexName |
|
166 * @return Table |
|
167 */ |
|
168 public function addUniqueIndex(array $columnNames, $indexName = null) |
|
169 { |
|
170 if ($indexName == null) { |
|
171 $indexName = $this->_generateIdentifierName( |
|
172 array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength() |
|
173 ); |
|
174 } |
|
175 |
|
176 return $this->_createIndex($columnNames, $indexName, true, false); |
|
177 } |
|
178 |
|
179 /** |
|
180 * Check if an index begins in the order of the given columns. |
|
181 * |
|
182 * @param array $columnsNames |
|
183 * @return bool |
|
184 */ |
|
185 public function columnsAreIndexed(array $columnsNames) |
|
186 { |
|
187 foreach ($this->getIndexes() AS $index) { |
|
188 /* @var $index Index */ |
|
189 if ($index->spansColumns($columnsNames)) { |
|
190 return true; |
|
191 } |
|
192 } |
|
193 return false; |
|
194 } |
|
195 |
|
196 /** |
|
197 * |
|
198 * @param array $columnNames |
|
199 * @param string $indexName |
|
200 * @param bool $isUnique |
|
201 * @param bool $isPrimary |
|
202 * @return Table |
|
203 */ |
|
204 private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary) |
|
205 { |
|
206 if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) { |
|
207 throw SchemaException::indexNameInvalid($indexName); |
|
208 } |
|
209 |
|
210 foreach ($columnNames AS $columnName => $indexColOptions) { |
|
211 if (is_numeric($columnName) && is_string($indexColOptions)) { |
|
212 $columnName = $indexColOptions; |
|
213 } |
|
214 |
|
215 if ( ! $this->hasColumn($columnName)) { |
|
216 throw SchemaException::columnDoesNotExist($columnName, $this->_name); |
|
217 } |
|
218 } |
|
219 $this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary)); |
|
220 return $this; |
|
221 } |
|
222 |
|
223 /** |
|
224 * @param string $columnName |
|
225 * @param string $columnType |
|
226 * @param array $options |
|
227 * @return Column |
|
228 */ |
|
229 public function addColumn($columnName, $typeName, array $options=array()) |
|
230 { |
|
231 $column = new Column($columnName, Type::getType($typeName), $options); |
|
232 |
|
233 $this->_addColumn($column); |
|
234 return $column; |
|
235 } |
|
236 |
|
237 /** |
|
238 * Rename Column |
|
239 * |
|
240 * @param string $oldColumnName |
|
241 * @param string $newColumnName |
|
242 * @return Table |
|
243 */ |
|
244 public function renameColumn($oldColumnName, $newColumnName) |
|
245 { |
|
246 $column = $this->getColumn($oldColumnName); |
|
247 $this->dropColumn($oldColumnName); |
|
248 |
|
249 $column->_setName($newColumnName); |
|
250 return $this; |
|
251 } |
|
252 |
|
253 /** |
|
254 * Change Column Details |
|
255 * |
|
256 * @param string $columnName |
|
257 * @param array $options |
|
258 * @return Table |
|
259 */ |
|
260 public function changeColumn($columnName, array $options) |
|
261 { |
|
262 $column = $this->getColumn($columnName); |
|
263 $column->setOptions($options); |
|
264 return $this; |
|
265 } |
|
266 |
|
267 /** |
|
268 * Drop Column from Table |
|
269 * |
|
270 * @param string $columnName |
|
271 * @return Table |
|
272 */ |
|
273 public function dropColumn($columnName) |
|
274 { |
|
275 $columnName = strtolower($columnName); |
|
276 $column = $this->getColumn($columnName); |
|
277 unset($this->_columns[$columnName]); |
|
278 return $this; |
|
279 } |
|
280 |
|
281 |
|
282 /** |
|
283 * Add a foreign key constraint |
|
284 * |
|
285 * Name is inferred from the local columns |
|
286 * |
|
287 * @param Table $foreignTable |
|
288 * @param array $localColumns |
|
289 * @param array $foreignColumns |
|
290 * @param array $options |
|
291 * @param string $constraintName |
|
292 * @return Table |
|
293 */ |
|
294 public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null) |
|
295 { |
|
296 $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength()); |
|
297 return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options); |
|
298 } |
|
299 |
|
300 /** |
|
301 * Add a foreign key constraint |
|
302 * |
|
303 * Name is to be generated by the database itsself. |
|
304 * |
|
305 * @deprecated Use {@link addForeignKeyConstraint} |
|
306 * @param Table $foreignTable |
|
307 * @param array $localColumns |
|
308 * @param array $foreignColumns |
|
309 * @param array $options |
|
310 * @return Table |
|
311 */ |
|
312 public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) |
|
313 { |
|
314 return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options); |
|
315 } |
|
316 |
|
317 /** |
|
318 * Add a foreign key constraint with a given name |
|
319 * |
|
320 * @deprecated Use {@link addForeignKeyConstraint} |
|
321 * @param string $name |
|
322 * @param Table $foreignTable |
|
323 * @param array $localColumns |
|
324 * @param array $foreignColumns |
|
325 * @param array $options |
|
326 * @return Table |
|
327 */ |
|
328 public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) |
|
329 { |
|
330 if ($foreignTable instanceof Table) { |
|
331 $foreignTableName = $foreignTable->getName(); |
|
332 |
|
333 foreach ($foreignColumnNames AS $columnName) { |
|
334 if ( ! $foreignTable->hasColumn($columnName)) { |
|
335 throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); |
|
336 } |
|
337 } |
|
338 } else { |
|
339 $foreignTableName = $foreignTable; |
|
340 } |
|
341 |
|
342 foreach ($localColumnNames AS $columnName) { |
|
343 if ( ! $this->hasColumn($columnName)) { |
|
344 throw SchemaException::columnDoesNotExist($columnName, $this->_name); |
|
345 } |
|
346 } |
|
347 |
|
348 $constraint = new ForeignKeyConstraint( |
|
349 $localColumnNames, $foreignTableName, $foreignColumnNames, $name, $options |
|
350 ); |
|
351 $this->_addForeignKeyConstraint($constraint); |
|
352 |
|
353 return $this; |
|
354 } |
|
355 |
|
356 /** |
|
357 * @param string $name |
|
358 * @param string $value |
|
359 * @return Table |
|
360 */ |
|
361 public function addOption($name, $value) |
|
362 { |
|
363 $this->_options[$name] = $value; |
|
364 return $this; |
|
365 } |
|
366 |
|
367 /** |
|
368 * @param Column $column |
|
369 */ |
|
370 protected function _addColumn(Column $column) |
|
371 { |
|
372 $columnName = $column->getName(); |
|
373 $columnName = strtolower($columnName); |
|
374 |
|
375 if (isset($this->_columns[$columnName])) { |
|
376 throw SchemaException::columnAlreadyExists($this->getName(), $columnName); |
|
377 } |
|
378 |
|
379 $this->_columns[$columnName] = $column; |
|
380 } |
|
381 |
|
382 /** |
|
383 * Add index to table |
|
384 * |
|
385 * @param Index $indexCandidate |
|
386 * @return Table |
|
387 */ |
|
388 protected function _addIndex(Index $indexCandidate) |
|
389 { |
|
390 // check for duplicates |
|
391 foreach ($this->_indexes AS $existingIndex) { |
|
392 if ($indexCandidate->isFullfilledBy($existingIndex)) { |
|
393 return $this; |
|
394 } |
|
395 } |
|
396 |
|
397 $indexName = $indexCandidate->getName(); |
|
398 $indexName = strtolower($indexName); |
|
399 |
|
400 if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) { |
|
401 throw SchemaException::indexAlreadyExists($indexName, $this->_name); |
|
402 } |
|
403 |
|
404 // remove overruled indexes |
|
405 foreach ($this->_indexes AS $idxKey => $existingIndex) { |
|
406 if ($indexCandidate->overrules($existingIndex)) { |
|
407 unset($this->_indexes[$idxKey]); |
|
408 } |
|
409 } |
|
410 |
|
411 if ($indexCandidate->isPrimary()) { |
|
412 $this->_primaryKeyName = $indexName; |
|
413 } |
|
414 |
|
415 $this->_indexes[$indexName] = $indexCandidate; |
|
416 return $this; |
|
417 } |
|
418 |
|
419 /** |
|
420 * @param ForeignKeyConstraint $constraint |
|
421 */ |
|
422 protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) |
|
423 { |
|
424 $constraint->setLocalTable($this); |
|
425 |
|
426 if(strlen($constraint->getName())) { |
|
427 $name = $constraint->getName(); |
|
428 } else { |
|
429 $name = $this->_generateIdentifierName( |
|
430 array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength() |
|
431 ); |
|
432 } |
|
433 $name = strtolower($name); |
|
434 |
|
435 $this->_fkConstraints[$name] = $constraint; |
|
436 // add an explicit index on the foreign key columns. If there is already an index that fullfils this requirements drop the request. |
|
437 // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes |
|
438 // lead to duplicates. This creates compuation overhead in this case, however no duplicate indexes are ever added (based on columns). |
|
439 $this->addIndex($constraint->getColumns()); |
|
440 } |
|
441 |
|
442 /** |
|
443 * Does Table have a foreign key constraint with the given name? |
|
444 * * |
|
445 * @param string $constraintName |
|
446 * @return bool |
|
447 */ |
|
448 public function hasForeignKey($constraintName) |
|
449 { |
|
450 $constraintName = strtolower($constraintName); |
|
451 return isset($this->_fkConstraints[$constraintName]); |
|
452 } |
|
453 |
|
454 /** |
|
455 * @param string $constraintName |
|
456 * @return ForeignKeyConstraint |
|
457 */ |
|
458 public function getForeignKey($constraintName) |
|
459 { |
|
460 $constraintName = strtolower($constraintName); |
|
461 if(!$this->hasForeignKey($constraintName)) { |
|
462 throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); |
|
463 } |
|
464 |
|
465 return $this->_fkConstraints[$constraintName]; |
|
466 } |
|
467 |
|
468 /** |
|
469 * @return Column[] |
|
470 */ |
|
471 public function getColumns() |
|
472 { |
|
473 $columns = $this->_columns; |
|
474 |
|
475 $pkCols = array(); |
|
476 $fkCols = array(); |
|
477 |
|
478 if ($this->hasIndex($this->_primaryKeyName)) { |
|
479 $pkCols = $this->getPrimaryKey()->getColumns(); |
|
480 } |
|
481 foreach ($this->getForeignKeys() AS $fk) { |
|
482 /* @var $fk ForeignKeyConstraint */ |
|
483 $fkCols = array_merge($fkCols, $fk->getColumns()); |
|
484 } |
|
485 $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns))); |
|
486 |
|
487 uksort($columns, function($a, $b) use($colNames) { |
|
488 return (array_search($a, $colNames) >= array_search($b, $colNames)); |
|
489 }); |
|
490 return $columns; |
|
491 } |
|
492 |
|
493 |
|
494 /** |
|
495 * Does this table have a column with the given name? |
|
496 * |
|
497 * @param string $columnName |
|
498 * @return bool |
|
499 */ |
|
500 public function hasColumn($columnName) |
|
501 { |
|
502 $columnName = $this->trimQuotes(strtolower($columnName)); |
|
503 return isset($this->_columns[$columnName]); |
|
504 } |
|
505 |
|
506 /** |
|
507 * Get a column instance |
|
508 * |
|
509 * @param string $columnName |
|
510 * @return Column |
|
511 */ |
|
512 public function getColumn($columnName) |
|
513 { |
|
514 $columnName = strtolower($this->trimQuotes($columnName)); |
|
515 if (!$this->hasColumn($columnName)) { |
|
516 throw SchemaException::columnDoesNotExist($columnName, $this->_name); |
|
517 } |
|
518 |
|
519 return $this->_columns[$columnName]; |
|
520 } |
|
521 |
|
522 /** |
|
523 * @return Index |
|
524 */ |
|
525 public function getPrimaryKey() |
|
526 { |
|
527 return $this->getIndex($this->_primaryKeyName); |
|
528 } |
|
529 |
|
530 /** |
|
531 * Check if this table has a primary key. |
|
532 * |
|
533 * @return bool |
|
534 */ |
|
535 public function hasPrimaryKey() |
|
536 { |
|
537 return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName)); |
|
538 } |
|
539 |
|
540 /** |
|
541 * @param string $indexName |
|
542 * @return bool |
|
543 */ |
|
544 public function hasIndex($indexName) |
|
545 { |
|
546 $indexName = strtolower($indexName); |
|
547 return (isset($this->_indexes[$indexName])); |
|
548 } |
|
549 |
|
550 /** |
|
551 * @param string $indexName |
|
552 * @return Index |
|
553 */ |
|
554 public function getIndex($indexName) |
|
555 { |
|
556 $indexName = strtolower($indexName); |
|
557 if (!$this->hasIndex($indexName)) { |
|
558 throw SchemaException::indexDoesNotExist($indexName, $this->_name); |
|
559 } |
|
560 return $this->_indexes[$indexName]; |
|
561 } |
|
562 |
|
563 /** |
|
564 * @return array |
|
565 */ |
|
566 public function getIndexes() |
|
567 { |
|
568 return $this->_indexes; |
|
569 } |
|
570 |
|
571 /** |
|
572 * Get Constraints |
|
573 * |
|
574 * @return array |
|
575 */ |
|
576 public function getForeignKeys() |
|
577 { |
|
578 return $this->_fkConstraints; |
|
579 } |
|
580 |
|
581 public function hasOption($name) |
|
582 { |
|
583 return isset($this->_options[$name]); |
|
584 } |
|
585 |
|
586 public function getOption($name) |
|
587 { |
|
588 return $this->_options[$name]; |
|
589 } |
|
590 |
|
591 public function getOptions() |
|
592 { |
|
593 return $this->_options; |
|
594 } |
|
595 |
|
596 /** |
|
597 * @param Visitor $visitor |
|
598 */ |
|
599 public function visit(Visitor $visitor) |
|
600 { |
|
601 $visitor->acceptTable($this); |
|
602 |
|
603 foreach ($this->getColumns() AS $column) { |
|
604 $visitor->acceptColumn($this, $column); |
|
605 } |
|
606 |
|
607 foreach ($this->getIndexes() AS $index) { |
|
608 $visitor->acceptIndex($this, $index); |
|
609 } |
|
610 |
|
611 foreach ($this->getForeignKeys() AS $constraint) { |
|
612 $visitor->acceptForeignKey($this, $constraint); |
|
613 } |
|
614 } |
|
615 |
|
616 /** |
|
617 * Clone of a Table triggers a deep clone of all affected assets |
|
618 */ |
|
619 public function __clone() |
|
620 { |
|
621 foreach ($this->_columns AS $k => $column) { |
|
622 $this->_columns[$k] = clone $column; |
|
623 } |
|
624 foreach ($this->_indexes AS $k => $index) { |
|
625 $this->_indexes[$k] = clone $index; |
|
626 } |
|
627 foreach ($this->_fkConstraints AS $k => $fk) { |
|
628 $this->_fkConstraints[$k] = clone $fk; |
|
629 $this->_fkConstraints[$k]->setLocalTable($this); |
|
630 } |
|
631 } |
|
632 } |