Failed Conditions
Push — master ( 8d18a3...24b4eb )
by Marco
13s
created

Table   D

Complexity

Total Complexity 99

Size/Duplication

Total Lines 831
Duplicated Lines 6.62 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 96.19%

Importance

Changes 0
Metric Value
wmc 99
lcom 2
cbo 9
dl 55
loc 831
ccs 227
cts 236
cp 0.9619
rs 4.4444
c 0
b 0
f 0

43 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 5
A setSchemaConfig() 0 4 1
A _getMaxIdentifierLength() 0 8 2
A setPrimaryKey() 0 11 3
A addIndex() 10 10 2
A dropPrimaryKey() 0 5 1
A dropIndex() 8 8 2
A addUniqueIndex() 10 10 2
B renameIndex() 0 33 6
B _createIndex() 0 18 6
A addColumn() 0 8 1
A renameColumn() 0 6 1
A changeColumn() 0 7 1
A dropColumn() 0 7 1
A addForeignKeyConstraint() 0 6 2
A addUnnamedForeignKeyConstraint() 0 4 1
B addNamedForeignKeyConstraint() 0 23 6
A addOption() 0 6 1
A _addColumn() 0 11 2
D _addIndex() 0 30 10
B _addForeignKeyConstraint() 0 34 4
A hasForeignKey() 0 6 1
A getForeignKey() 9 9 2
A removeForeignKey() 9 9 2
A getColumns() 0 9 2
A getForeignKeyColumns() 0 9 2
A filterColumns() 0 6 1
A hasColumn() 0 6 1
A getColumn() 0 9 2
A getPrimaryKey() 0 8 2
A getPrimaryKeyColumns() 0 7 2
A hasPrimaryKey() 0 4 2
A hasIndex() 0 6 1
A getIndex() 9 9 2
A getIndexes() 0 4 1
A getForeignKeys() 0 4 1
A hasOption() 0 4 1
A getOption() 0 4 1
A getOptions() 0 4 1
A visit() 0 16 4
A __clone() 0 13 4
A normalizeIdentifier() 0 4 1
A columnsAreIndexed() 0 11 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Schema;
21
22
use Doctrine\DBAL\Types\Type;
23
use Doctrine\DBAL\Schema\Visitor\Visitor;
24
use Doctrine\DBAL\DBALException;
25
26
/**
27
 * Object Representation of a table.
28
 *
29
 * @link   www.doctrine-project.org
30
 * @since  2.0
31
 * @author Benjamin Eberlei <[email protected]>
32
 */
33
class Table extends AbstractAsset
34
{
35
    /**
36
     * @var string
37
     */
38
    protected $_name = null;
39
40
    /**
41
     * @var Column[]
42
     */
43
    protected $_columns = [];
44
45
    /**
46
     * @var Index[]
47
     */
48
    private $implicitIndexes = [];
49
50
    /**
51
     * @var Index[]
52
     */
53
    protected $_indexes = [];
54
55
    /**
56
     * @var string
57
     */
58
    protected $_primaryKeyName = false;
59
60
    /**
61
     * @var ForeignKeyConstraint[]
62
     */
63
    protected $_fkConstraints = [];
64
65
    /**
66
     * @var array
67
     */
68
    protected $_options = [];
69
70
    /**
71
     * @var SchemaConfig
72
     */
73
    protected $_schemaConfig = null;
74
75
    /**
76
     * @param string                 $tableName
77
     * @param Column[]               $columns
78
     * @param Index[]                $indexes
79
     * @param ForeignKeyConstraint[] $fkConstraints
80
     * @param integer                $idGeneratorType
81
     * @param array                  $options
82
     *
83
     * @throws DBALException
84
     */
85 753
    public function __construct($tableName, array $columns=[], array $indexes=[], array $fkConstraints=[], $idGeneratorType = 0, array $options=[])
0 ignored issues
show
Unused Code introduced by
The parameter $idGeneratorType is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
86
    {
87 753
        if (strlen($tableName) == 0) {
88 1
            throw DBALException::invalidTableName($tableName);
89
        }
90
91 752
        $this->_setName($tableName);
92
93 752
        foreach ($columns as $column) {
94 70
            $this->_addColumn($column);
95
        }
96
97 751
        foreach ($indexes as $idx) {
98 30
            $this->_addIndex($idx);
99
        }
100
101 749
        foreach ($fkConstraints as $constraint) {
102 6
            $this->_addForeignKeyConstraint($constraint);
103
        }
104
105 749
        $this->_options = $options;
106 749
    }
107
108
    /**
109
     * @param SchemaConfig $schemaConfig
110
     *
111
     * @return void
112
     */
113 61
    public function setSchemaConfig(SchemaConfig $schemaConfig)
114
    {
115 61
        $this->_schemaConfig = $schemaConfig;
116 61
    }
117
118
    /**
119
     * @return integer
120
     */
121 144
    protected function _getMaxIdentifierLength()
122
    {
123 144
        if ($this->_schemaConfig instanceof SchemaConfig) {
124 8
            return $this->_schemaConfig->getMaxIdentifierLength();
125
        } else {
126 136
            return 63;
127
        }
128
    }
129
130
    /**
131
     * Sets the Primary Key.
132
     *
133
     * @param array          $columns
134
     * @param string|boolean $indexName
135
     *
136
     * @return self
137
     */
138 314
    public function setPrimaryKey(array $columns, $indexName = false)
139
    {
140 314
        $this->_addIndex($this->_createIndex($columns, $indexName ?: "primary", true, true));
0 ignored issues
show
Bug introduced by
It seems like $indexName ?: 'primary' can also be of type boolean; however, Doctrine\DBAL\Schema\Table::_createIndex() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
141
142 314
        foreach ($columns as $columnName) {
143 314
            $column = $this->getColumn($columnName);
144 314
            $column->setNotnull(true);
145
        }
146
147 314
        return $this;
148
    }
149
150
    /**
151
     * @param array       $columnNames
152
     * @param string|null $indexName
153
     * @param array       $flags
154
     * @param array       $options
155
     *
156
     * @return self
157
     */
158 95 View Code Duplication
    public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
    {
160 95
        if ($indexName == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $indexName of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
161 26
            $indexName = $this->_generateIdentifierName(
162 26
                array_merge([$this->getName()], $columnNames), "idx", $this->_getMaxIdentifierLength()
163
            );
164
        }
165
166 95
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
167
    }
168
169
    /**
170
     * Drops the primary key from this table.
171
     *
172
     * @return void
173
     */
174 13
    public function dropPrimaryKey()
175
    {
176 13
        $this->dropIndex($this->_primaryKeyName);
177 13
        $this->_primaryKeyName = false;
0 ignored issues
show
Documentation Bug introduced by
The property $_primaryKeyName was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
178 13
    }
179
180
    /**
181
     * Drops an index from this table.
182
     *
183
     * @param string $indexName The index name.
184
     *
185
     * @return void
186
     *
187
     * @throws SchemaException If the index does not exist.
188
     */
189 25 View Code Duplication
    public function dropIndex($indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
190
    {
191 25
        $indexName = $this->normalizeIdentifier($indexName);
192 25
        if ( ! $this->hasIndex($indexName)) {
193
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
194
        }
195 25
        unset($this->_indexes[$indexName]);
196 25
    }
197
198
    /**
199
     * @param array       $columnNames
200
     * @param string|null $indexName
201
     * @param array       $options
202
     *
203
     * @return self
204
     */
205 29 View Code Duplication
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
    {
207 29
        if ($indexName === null) {
208 21
            $indexName = $this->_generateIdentifierName(
209 21
                array_merge([$this->getName()], $columnNames), "uniq", $this->_getMaxIdentifierLength()
210
            );
211
        }
212
213 29
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
214
    }
215
216
    /**
217
     * Renames an index.
218
     *
219
     * @param string      $oldIndexName The name of the index to rename from.
220
     * @param string|null $newIndexName The name of the index to rename to.
221
     *                                  If null is given, the index name will be auto-generated.
222
     *
223
     * @return self This table instance.
224
     *
225
     * @throws SchemaException if no index exists for the given current name
226
     *                         or if an index with the given new name already exists on this table.
227
     */
228 13
    public function renameIndex($oldIndexName, $newIndexName = null)
229
    {
230 13
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
231 13
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
232
233 13
        if ($oldIndexName === $normalizedNewIndexName) {
234 9
            return $this;
235
        }
236
237 13
        if ( ! $this->hasIndex($oldIndexName)) {
238 1
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
239
        }
240
241 12
        if ($this->hasIndex($normalizedNewIndexName)) {
242 1
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
243
        }
244
245 11
        $oldIndex = $this->_indexes[$oldIndexName];
246
247 11
        if ($oldIndex->isPrimary()) {
248 1
            $this->dropPrimaryKey();
249
250 1
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName);
251
        }
252
253 11
        unset($this->_indexes[$oldIndexName]);
254
255 11
        if ($oldIndex->isUnique()) {
256 2
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
257
        }
258
259 10
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
260
    }
261
262
    /**
263
     * Checks if an index begins in the order of the given columns.
264
     *
265
     * @param array $columnsNames
266
     *
267
     * @return boolean
268
     */
269 1
    public function columnsAreIndexed(array $columnsNames)
270
    {
271 1
        foreach ($this->getIndexes() as $index) {
272
            /* @var $index Index */
273 1
            if ($index->spansColumns($columnsNames)) {
274 1
                return true;
275
            }
276
        }
277
278
        return false;
279
    }
280
281
    /**
282
     * @param array   $columnNames
283
     * @param string  $indexName
284
     * @param boolean $isUnique
285
     * @param boolean $isPrimary
286
     * @param array   $flags
287
     * @param array   $options
288
     *
289
     * @return Index
290
     *
291
     * @throws SchemaException
292
     */
293 450
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
294
    {
295 450
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
296 1
            throw SchemaException::indexNameInvalid($indexName);
297
        }
298
299 449
        foreach ($columnNames as $columnName => $indexColOptions) {
300 448
            if (is_numeric($columnName) && is_string($indexColOptions)) {
301 448
                $columnName = $indexColOptions;
302
            }
303
304 448
            if ( ! $this->hasColumn($columnName)) {
305 448
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
306
            }
307
        }
308
309 448
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
310
    }
311
312
    /**
313
     * @param string $columnName
314
     * @param string $typeName
315
     * @param array  $options
316
     *
317
     * @return Column
318
     */
319 607
    public function addColumn($columnName, $typeName, array $options=[])
320
    {
321 607
        $column = new Column($columnName, Type::getType($typeName), $options);
322
323 607
        $this->_addColumn($column);
324
325 607
        return $column;
326
    }
327
328
    /**
329
     * Renames a Column.
330
     *
331
     * @param string $oldColumnName
332
     * @param string $newColumnName
333
     *
334
     * @deprecated
335
     *
336
     * @throws DBALException
337
     */
338
    public function renameColumn($oldColumnName, $newColumnName)
0 ignored issues
show
Unused Code introduced by
The parameter $oldColumnName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $newColumnName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
339
    {
340
        throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " .
341
            "the column instead. There is no fix available, because a schema diff cannot reliably detect if a " .
342
            "column was renamed or one column was created and another one dropped.");
343
    }
344
345
    /**
346
     * Change Column Details.
347
     *
348
     * @param string $columnName
349
     * @param array  $options
350
     *
351
     * @return self
352
     */
353 12
    public function changeColumn($columnName, array $options)
354
    {
355 12
        $column = $this->getColumn($columnName);
356 12
        $column->setOptions($options);
357
358 12
        return $this;
359
    }
360
361
    /**
362
     * Drops a Column from the Table.
363
     *
364
     * @param string $columnName
365
     *
366
     * @return self
367
     */
368 9
    public function dropColumn($columnName)
369
    {
370 9
        $columnName = $this->normalizeIdentifier($columnName);
371 9
        unset($this->_columns[$columnName]);
372
373 9
        return $this;
374
    }
375
376
    /**
377
     * Adds a foreign key constraint.
378
     *
379
     * Name is inferred from the local columns.
380
     *
381
     * @param Table|string $foreignTable Table schema instance or table name
382
     * @param array        $localColumnNames
383
     * @param array        $foreignColumnNames
384
     * @param array        $options
385
     * @param string|null  $constraintName
386
     *
387
     * @return self
388
     */
389 95
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=[], $constraintName = null)
390
    {
391 95
        $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
392
393 95
        return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\DBAL\Schema\Tab...dForeignKeyConstraint() has been deprecated with message: Use {@link addForeignKeyConstraint}

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
394
    }
395
396
    /**
397
     * Adds a foreign key constraint.
398
     *
399
     * Name is to be generated by the database itself.
400
     *
401
     * @deprecated Use {@link addForeignKeyConstraint}
402
     *
403
     * @param Table|string $foreignTable Table schema instance or table name
404
     * @param array        $localColumnNames
405
     * @param array        $foreignColumnNames
406
     * @param array        $options
407
     *
408
     * @return self
409
     */
410 5
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=[])
411
    {
412 5
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
413
    }
414
415
    /**
416
     * Adds a foreign key constraint with a given name.
417
     *
418
     * @deprecated Use {@link addForeignKeyConstraint}
419
     *
420
     * @param string       $name
421
     * @param Table|string $foreignTable Table schema instance or table name
422
     * @param array        $localColumnNames
423
     * @param array        $foreignColumnNames
424
     * @param array        $options
425
     *
426
     * @return self
427
     *
428
     * @throws SchemaException
429
     */
430 96
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=[])
431
    {
432 96
        if ($foreignTable instanceof Table) {
433 50
            foreach ($foreignColumnNames as $columnName) {
434 50
                if ( ! $foreignTable->hasColumn($columnName)) {
435 50
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
436
                }
437
            }
438
        }
439
440 95
        foreach ($localColumnNames as $columnName) {
441 95
            if ( ! $this->hasColumn($columnName)) {
442 95
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
443
            }
444
        }
445
446 94
        $constraint = new ForeignKeyConstraint(
447 94
            $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options
448
        );
449 94
        $this->_addForeignKeyConstraint($constraint);
450
451 94
        return $this;
452
    }
453
454
    /**
455
     * @param string $name
456
     * @param string $value
457
     *
458
     * @return self
459
     */
460 35
    public function addOption($name, $value)
461
    {
462 35
        $this->_options[$name] = $value;
463
464 35
        return $this;
465
    }
466
467
    /**
468
     * @param Column $column
469
     *
470
     * @return void
471
     *
472
     * @throws SchemaException
473
     */
474 637
    protected function _addColumn(Column $column)
475
    {
476 637
        $columnName = $column->getName();
477 637
        $columnName = $this->normalizeIdentifier($columnName);
478
479 637
        if (isset($this->_columns[$columnName])) {
480 1
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
481
        }
482
483 637
        $this->_columns[$columnName] = $column;
484 637
    }
485
486
    /**
487
     * Adds an index to the table.
488
     *
489
     * @param Index $indexCandidate
490
     *
491
     * @return self
492
     *
493
     * @throws SchemaException
494
     */
495 455
    protected function _addIndex(Index $indexCandidate)
496
    {
497 455
        $indexName = $indexCandidate->getName();
498 455
        $indexName = $this->normalizeIdentifier($indexName);
499 455
        $replacedImplicitIndexes = [];
500
501 455
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
502 28
            if ($implicitIndex->isFullfilledBy($indexCandidate) && isset($this->_indexes[$name])) {
503 28
                $replacedImplicitIndexes[] = $name;
504
            }
505
        }
506
507 455
        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
508 455
            ($this->_primaryKeyName != false && $indexCandidate->isPrimary())
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $this->_primaryKeyName of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
509
        ) {
510 2
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
511
        }
512
513 455
        foreach ($replacedImplicitIndexes as $name) {
514 4
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
515
        }
516
517 455
        if ($indexCandidate->isPrimary()) {
518 316
            $this->_primaryKeyName = $indexName;
519
        }
520
521 455
        $this->_indexes[$indexName] = $indexCandidate;
522
523 455
        return $this;
524
    }
525
526
    /**
527
     * @param ForeignKeyConstraint $constraint
528
     *
529
     * @return void
530
     */
531 97
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
532
    {
533 97
        $constraint->setLocalTable($this);
534
535 97
        if (strlen($constraint->getName())) {
536 95
            $name = $constraint->getName();
537
        } else {
538 2
            $name = $this->_generateIdentifierName(
539 2
                array_merge((array) $this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
540
            );
541
        }
542 97
        $name = $this->normalizeIdentifier($name);
543
544 97
        $this->_fkConstraints[$name] = $constraint;
545
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
548
        // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
549 97
        $indexName = $this->_generateIdentifierName(
550 97
            array_merge([$this->getName()], $constraint->getColumns()),
551 97
            "idx",
552 97
            $this->_getMaxIdentifierLength()
553
        );
554 97
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
555
556 97
        foreach ($this->_indexes as $existingIndex) {
557 73
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
558 73
                return;
559
            }
560
        }
561
562 73
        $this->_addIndex($indexCandidate);
563 73
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
564 73
    }
565
566
    /**
567
     * Returns whether this table has a foreign key constraint with the given name.
568
     *
569
     * @param string $constraintName
570
     *
571
     * @return boolean
572
     */
573 10
    public function hasForeignKey($constraintName)
574
    {
575 10
        $constraintName = $this->normalizeIdentifier($constraintName);
576
577 10
        return isset($this->_fkConstraints[$constraintName]);
578
    }
579
580
    /**
581
     * Returns the foreign key constraint with the given name.
582
     *
583
     * @param string $constraintName The constraint name.
584
     *
585
     * @return ForeignKeyConstraint
586
     *
587
     * @throws SchemaException If the foreign key does not exist.
588
     */
589 8 View Code Duplication
    public function getForeignKey($constraintName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
    {
591 8
        $constraintName = $this->normalizeIdentifier($constraintName);
592 8
        if (!$this->hasForeignKey($constraintName)) {
593
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
594
        }
595
596 8
        return $this->_fkConstraints[$constraintName];
597
    }
598
599
    /**
600
     * Removes the foreign key constraint with the given name.
601
     *
602
     * @param string $constraintName The constraint name.
603
     *
604
     * @return void
605
     *
606
     * @throws SchemaException
607
     */
608 10 View Code Duplication
    public function removeForeignKey($constraintName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
609
    {
610 10
        $constraintName = $this->normalizeIdentifier($constraintName);
611 10
        if (!$this->hasForeignKey($constraintName)) {
612
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
613
        }
614
615 10
        unset($this->_fkConstraints[$constraintName]);
616 10
    }
617
618
    /**
619
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
620
     * @return Column[]
621
     */
622 447
    public function getColumns()
623
    {
624 447
        $primaryKeyColumns = [];
625 447
        if ($this->hasPrimaryKey()) {
626 209
            $primaryKeyColumns = $this->filterColumns($this->getPrimaryKey()->getColumns());
627
        }
628
629 447
        return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns);
630
    }
631
632
    /**
633
     * Returns foreign key columns
634
     * @return Column[]
635
     */
636 447
    private function getForeignKeyColumns()
637
    {
638 447
        $foreignKeyColumns = [];
639 447
        foreach ($this->getForeignKeys() as $foreignKey) {
640
            /* @var $foreignKey ForeignKeyConstraint */
641 43
            $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns());
642
        }
643 447
        return $this->filterColumns($foreignKeyColumns);
644
    }
645
646
    /**
647
     * Returns only columns that have specified names
648
     * @param array $columnNames
649
     * @return Column[]
650
     */
651
    private function filterColumns(array $columnNames)
652
    {
653 447
        return array_filter($this->_columns, function ($columnName) use ($columnNames) {
654 423
            return in_array($columnName, $columnNames, true);
655 447
        }, ARRAY_FILTER_USE_KEY);
656
    }
657
658
    /**
659
     * Returns whether this table has a Column with the given name.
660
     *
661
     * @param string $columnName The column name.
662
     *
663
     * @return boolean
664
     */
665 518
    public function hasColumn($columnName)
666
    {
667 518
        $columnName = $this->normalizeIdentifier($columnName);
668
669 518
        return isset($this->_columns[$columnName]);
670
    }
671
672
    /**
673
     * Returns the Column with the given name.
674
     *
675
     * @param string $columnName The column name.
676
     *
677
     * @return Column
678
     *
679
     * @throws SchemaException If the column does not exist.
680
     */
681 397
    public function getColumn($columnName)
682
    {
683 397
        $columnName = $this->normalizeIdentifier($columnName);
684 397
        if ( ! $this->hasColumn($columnName)) {
685 1
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
686
        }
687
688 396
        return $this->_columns[$columnName];
689
    }
690
691
    /**
692
     * Returns the primary key.
693
     *
694
     * @return Index|null The primary key, or null if this Table has no primary key.
695
     */
696 214
    public function getPrimaryKey()
697
    {
698 214
        if ( ! $this->hasPrimaryKey()) {
699
            return null;
700
        }
701
702 214
        return $this->getIndex($this->_primaryKeyName);
703
    }
704
705
    /**
706
     * Returns the primary key columns.
707
     *
708
     * @return array
709
     *
710
     * @throws DBALException
711
     */
712 9
    public function getPrimaryKeyColumns()
713
    {
714 9
        if ( ! $this->hasPrimaryKey()) {
715
            throw new DBALException("Table " . $this->getName() . " has no primary key.");
716
        }
717 9
        return $this->getPrimaryKey()->getColumns();
718
    }
719
720
    /**
721
     * Returns whether this table has a primary key.
722
     *
723
     * @return boolean
724
     */
725 455
    public function hasPrimaryKey()
726
    {
727 455
        return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
728
    }
729
730
    /**
731
     * Returns whether this table has an Index with the given name.
732
     *
733
     * @param string $indexName The index name.
734
     *
735
     * @return boolean
736
     */
737 264
    public function hasIndex($indexName)
738
    {
739 264
        $indexName = $this->normalizeIdentifier($indexName);
740
741 264
        return (isset($this->_indexes[$indexName]));
742
    }
743
744
    /**
745
     * Returns the Index with the given name.
746
     *
747
     * @param string $indexName The index name.
748
     *
749
     * @return Index
750
     *
751
     * @throws SchemaException If the index does not exist.
752
     */
753 242 View Code Duplication
    public function getIndex($indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
754
    {
755 242
        $indexName = $this->normalizeIdentifier($indexName);
756 242
        if ( ! $this->hasIndex($indexName)) {
757 1
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
758
        }
759
760 241
        return $this->_indexes[$indexName];
761
    }
762
763
    /**
764
     * @return Index[]
765
     */
766 423
    public function getIndexes()
767
    {
768 423
        return $this->_indexes;
769
    }
770
771
    /**
772
     * Returns the foreign key constraints.
773
     *
774
     * @return ForeignKeyConstraint[]
775
     */
776 460
    public function getForeignKeys()
777
    {
778 460
        return $this->_fkConstraints;
779
    }
780
781
    /**
782
     * @param string $name
783
     *
784
     * @return boolean
785
     */
786 51
    public function hasOption($name)
787
    {
788 51
        return isset($this->_options[$name]);
789
    }
790
791
    /**
792
     * @param string $name
793
     *
794
     * @return mixed
795
     */
796 5
    public function getOption($name)
797
    {
798 5
        return $this->_options[$name];
799
    }
800
801
    /**
802
     * @return array
803
     */
804 334
    public function getOptions()
805
    {
806 334
        return $this->_options;
807
    }
808
809
    /**
810
     * @param Visitor $visitor
811
     *
812
     * @return void
813
     */
814 18
    public function visit(Visitor $visitor)
815
    {
816 18
        $visitor->acceptTable($this);
817
818 18
        foreach ($this->getColumns() as $column) {
819 15
            $visitor->acceptColumn($this, $column);
820
        }
821
822 18
        foreach ($this->getIndexes() as $index) {
823 11
            $visitor->acceptIndex($this, $index);
824
        }
825
826 18
        foreach ($this->getForeignKeys() as $constraint) {
827 4
            $visitor->acceptForeignKey($this, $constraint);
828
        }
829 18
    }
830
831
    /**
832
     * Clone of a Table triggers a deep clone of all affected assets.
833
     *
834
     * @return void
835
     */
836 50
    public function __clone()
837
    {
838 50
        foreach ($this->_columns as $k => $column) {
839 49
            $this->_columns[$k] = clone $column;
840
        }
841 50
        foreach ($this->_indexes as $k => $index) {
842 28
            $this->_indexes[$k] = clone $index;
843
        }
844 50
        foreach ($this->_fkConstraints as $k => $fk) {
845 6
            $this->_fkConstraints[$k] = clone $fk;
846 6
            $this->_fkConstraints[$k]->setLocalTable($this);
847
        }
848 50
    }
849
850
    /**
851
     * Normalizes a given identifier.
852
     *
853
     * Trims quotes and lowercases the given identifier.
854
     *
855
     * @param string $identifier The identifier to normalize.
856
     *
857
     * @return string The normalized identifier.
858
     */
859 641
    private function normalizeIdentifier($identifier)
860
    {
861 641
        return $this->trimQuotes(strtolower($identifier));
862
    }
863
}
864