Completed
Pull Request — master (#1175)
by
unknown
01:31
created

Table::addIndex()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 3
nop 2
crap 3
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db
28
 */
29
namespace Phinx\Db;
30
31
use Phinx\Db\Table\Column;
32
use Phinx\Db\Table\CustomColumn;
33
use Phinx\Db\Table\Index;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Adapter\AdapterInterface;
36
37
/**
38
 *
39
 * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
40
 */
41
class Table
42
{
43
    /**
44
     * @var string
45
     */
46
    protected $name;
47
48
    /**
49
     * @var array
50
     */
51
    protected $options = [];
52
53
    /**
54
     * @var AdapterInterface
55
     */
56
    protected $adapter;
57
58
    /**
59
     * @var array
60
     */
61
    protected $columns = [];
62
63
    /**
64
     * @var array
65
     */
66
    protected $indexes = [];
67
68
    /**
69
     * @var ForeignKey[]
70
     */
71
    protected $foreignKeys = [];
72
73
    /**
74
     * @var array
75
     */
76
    protected $data = [];
77
78
    /**
79
     * Class Constuctor.
80
     *
81
     * @param string $name Table Name
82
     * @param array $options Options
83
     * @param AdapterInterface $adapter Database Adapter
84 239
     */
85
    public function __construct($name, $options = [], AdapterInterface $adapter = null)
86 239
    {
87 239
        $this->setName($name);
88
        $this->setOptions($options);
89 239
90 231
        if ($adapter !== null) {
91 231
            $this->setAdapter($adapter);
92 239
        }
93
    }
94
95
    /**
96
     * Sets the table name.
97
     *
98
     * @param string $name Table Name
99
     * @return Table
100 239
     */
101
    public function setName($name)
102 239
    {
103 239
        $this->name = $name;
104
        return $this;
105
    }
106
107
    /**
108
     * Gets the table name.
109
     *
110
     * @return string
111 215
     */
112
    public function getName()
113 215
    {
114
        return $this->name;
115
    }
116
117
    /**
118
     * Sets the table options.
119
     *
120
     * @param array $options
121
     * @return Table
122 239
     */
123
    public function setOptions($options)
124 239
    {
125 239
        $this->options = $options;
126
        return $this;
127
    }
128
129
    /**
130
     * Gets the table options.
131
     *
132
     * @return array
133 189
     */
134
    public function getOptions()
135 189
    {
136
        return $this->options;
137
    }
138
139
    /**
140
     * Sets the database adapter.
141
     *
142
     * @param AdapterInterface $adapter Database Adapter
143
     * @return Table
144 231
     */
145
    public function setAdapter(AdapterInterface $adapter)
146 231
    {
147 231
        $this->adapter = $adapter;
148
        return $this;
149
    }
150
151
    /**
152
     * Gets the database adapter.
153
     *
154
     * @return AdapterInterface
155 225
     */
156
    public function getAdapter()
157 225
    {
158
        return $this->adapter;
159
    }
160
161
    /**
162
     * Does the table exist?
163
     *
164
     * @return boolean
165 195
     */
166
    public function exists()
167 195
    {
168
        return $this->getAdapter()->hasTable($this->getName());
169
    }
170
171
    /**
172
     * Drops the database table.
173
     *
174
     * @return void
175 1
     */
176
    public function drop()
177 1
    {
178 1
        $this->getAdapter()->dropTable($this->getName());
179
    }
180
181
    /**
182
     * Renames the database table.
183
     *
184
     * @param string $newTableName New Table Name
185
     * @return Table
186 3
     */
187
    public function rename($newTableName)
188 3
    {
189 3
        $this->getAdapter()->renameTable($this->getName(), $newTableName);
190 3
        $this->setName($newTableName);
191
        return $this;
192
    }
193
194
    /**
195
     * Sets an array of columns waiting to be committed.
196
     * Use setPendingColumns
197
     *
198
     * @deprecated
199
     * @param array $columns Columns
200
     * @return Table
201
     */
202
    public function setColumns($columns)
203
    {
204
        $this->setPendingColumns($columns);
205
        return $this;
206
    }
207
208
    /**
209
     * Gets an array of the table columns.
210
     *
211 10
     * @return Column[]
212
     */
213 10
    public function getColumns()
214
    {
215
        return $this->getAdapter()->getColumns($this->getName());
216
    }
217
218
    /**
219
     * Sets an array of columns waiting to be committed.
220
     *
221
     * @param array $columns Columns
222 196
     * @return Table
223
     */
224 196
    public function setPendingColumns($columns)
225 196
    {
226
        $this->columns = $columns;
227
        return $this;
228
    }
229
230
    /**
231
     * Gets an array of columns waiting to be committed.
232
     *
233 204
     * @return Column[]
234
     */
235 204
    public function getPendingColumns()
236
    {
237
        return $this->columns;
238
    }
239
240
    /**
241
     * Sets an array of columns waiting to be indexed.
242
     *
243
     * @param array $indexes Indexes
244 196
     * @return Table
245
     */
246 196
    public function setIndexes($indexes)
247 196
    {
248
        $this->indexes = $indexes;
249
        return $this;
250
    }
251
252
    /**
253
     * Gets an array of indexes waiting to be committed.
254
     *
255 191
     * @return array
256
     */
257 191
    public function getIndexes()
258
    {
259
        return $this->indexes;
260
    }
261
262
    /**
263
     * Sets an array of foreign keys waiting to be commited.
264
     *
265
     * @param ForeignKey[] $foreignKeys foreign keys
266 196
     * @return Table
267
     */
268 196
    public function setForeignKeys($foreignKeys)
269 196
    {
270
        $this->foreignKeys = $foreignKeys;
271
        return $this;
272
    }
273
274
    /**
275
     * Gets an array of foreign keys waiting to be commited.
276
     *
277 192
     * @return array|ForeignKey[]
278
     */
279 192
    public function getForeignKeys()
280
    {
281
        return $this->foreignKeys;
282
    }
283
284
    /**
285
     * Sets an array of data to be inserted.
286
     *
287
     * @param array $data Data
288 196
     * @return Table
289
     */
290 196
    public function setData($data)
291 196
    {
292
        $this->data = $data;
293
        return $this;
294
    }
295
296
    /**
297
     * Gets the data waiting to be inserted.
298
     *
299 197
     * @return array
300
     */
301 197
    public function getData()
302
    {
303
        return $this->data;
304
    }
305
306
    /**
307
     * Resets all of the pending table changes.
308
     *
309 196
     * @return void
310
     */
311 196
    public function reset()
312 196
    {
313 196
        $this->setPendingColumns([]);
314 196
        $this->setIndexes([]);
315 196
        $this->setForeignKeys([]);
316
        $this->setData([]);
317
    }
318
319
    /**
320
     * Add a table column.
321
     *
322
     * Type can be: string, text, integer, float, decimal, datetime, timestamp,
323
     * time, date, binary, boolean.
324
     *
325
     * Valid options can be: limit, default, null, precision or scale.
326
     *
327
     * @param string|Column $columnName Column Name
328
     * @param string $type Column Type
329
     * @param array $options Column Options
330
     * @throws \RuntimeException
331
     * @throws \InvalidArgumentException
332 210
     * @return Table
333
     */
334
    public function addColumn($columnName, $type = null, $options = [])
335 210
    {
336 1
        // we need an adapter set to add a column
337
        if ($this->getAdapter() === null) {
338
            throw new \RuntimeException('An adapter must be specified to add a column.');
339
        }
340 209
341 207
        // create a new column object if only strings were supplied
342 207 View Code Duplication
        if (!$columnName instanceof Column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
343 207
            $column = new Column();
344 207
            $column->setName($columnName);
345 207
            $column->setType($type);
346 2
            $column->setOptions($options); // map options to column methods
347
        } else {
348
            $column = $columnName;
349
        }
350 209
351 1
        // Delegate to Adapters to check column type
352 1
        if (!$this->getAdapter()->isValidColumnType($column)) {
353 1
            throw new \InvalidArgumentException(sprintf(
354 1
                'An invalid column type "%s" was specified for column "%s".',
355 1
                $column->getType(),
356
                $column->getName()
357
            ));
358 208
        }
359 208
360
        $this->columns[] = $column;
361
        return $this;
362
    }
363
364
    /**
365
     * Add a table column with a custom type definition.
366
     *
367
     * Type can be any string that you expect your DBMS to understand
368 1
     *
369
     * Valid options can be:  default or null.
370 1
     *
371 1
     * For widely supported types @see addColumn
372
     *
373
     * @param string|CustomColumn $columnName Column Name
374
     * @param string $type Column Type
375
     * @param array $options Column Options
376
     * @return Table
377
     */
378
    public function addCustomColumn($columnName, $type = null, $options = array())
379
    {
380
        // create a new CustomColumn object if only strings were supplied
381 4 View Code Duplication
        if (!$columnName instanceof CustomColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
382
            $column = new CustomColumn();
383 4
            $column->setName($columnName);
384 4
            $column->setType($type);
385
            $column->setOptions($options); // map options to column methods
386
        } else {
387
            $column = $columnName;
388
        }
389
390
        $this->columns[] = $column;
391
        return $this;
392
    }
393
394
    /**
395 17
     * Remove a table column.
396
     *
397
     * @param string $columnName Column Name
398 17
     * @return Table
399 4
     */
400 4
    public function removeColumn($columnName)
401 4
    {
402 4
        $this->getAdapter()->dropColumn($this->getName(), $columnName);
403 13
        return $this;
404
    }
405
406
    /**
407 17
     * Rename a table column.
408 15
     *
409 15
     * @param string $oldName Old Column Name
410
     * @param string $newName New Column Name
411 17
     * @return Table
412 17
     */
413
    public function renameColumn($oldName, $newName)
414
    {
415
        $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName);
416
        return $this;
417
    }
418
419
    /**
420
     * Change a table column type.
421 89
     *
422
     * @param string        $columnName    Column Name
423 89
     * @param string|Column $newColumnType New Column Type
424
     * @param array         $options       Options
425
     * @return Table
426
     */
427
    public function changeColumn($columnName, $newColumnType, $options = [])
428
    {
429
        // create a column object if one wasn't supplied
430
        if (!$newColumnType instanceof Column) {
431
            $newColumn = new Column();
432
            $newColumn->setType($newColumnType);
433
            $newColumn->setOptions($options);
434
        } else {
435 29
            $newColumn = $newColumnType;
436
        }
437
438 29
        // if the name was omitted use the existing column name
439 28
        if ($newColumn->getName() === null || strlen($newColumn->getName()) === 0) {
440 28
            $newColumn->setName($columnName);
441 22
        }
442 22
443 28
        $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn);
444 28
        return $this;
445 28
    }
446 1
447
    /**
448
     * Checks to see if a column exists.
449 29
     *
450 29
     * @param string $columnName Column Name
451
     * @return boolean
452
     */
453
    public function hasColumn($columnName)
454
    {
455
        return $this->getAdapter()->hasColumn($this->getName(), $columnName);
456
    }
457
458
    /**
459 1
     * Add an index to a database table.
460
     *
461 1
     * In $options you can specific unique = true/false or name (index name).
462 1
     *
463
     * @param string|array|Index $columns Table Column(s)
464
     * @param array $options Index Options
465
     * @return Table
466
     */
467
    public function addIndex($columns, $options = [])
468
    {
469
        // create a new index object if strings or an array of strings were supplied
470
        if (!$columns instanceof Index) {
471 1
            $index = new Index();
472
            if (is_string($columns)) {
473 1
                $columns = [$columns]; // str to array
474 1
            }
475
            $index->setColumns($columns);
476
            $index->setOptions($options);
477
        } else {
478
            $index = $columns;
479
        }
480
481
        $this->indexes[] = $index;
482
        return $this;
483
    }
484 12
485
    /**
486 12
     * Removes the given index from a table.
487
     *
488
     * @param array $columns Columns
489
     * @return Table
490
     */
491
    public function removeIndex($columns)
492
    {
493
        $this->getAdapter()->dropIndex($this->getName(), $columns);
494
        return $this;
495
    }
496
497
    /**
498
     * Removes the given index identified by its name from a table.
499
     *
500
     * @param string $name Index name
501 8
     * @return Table
502
     */
503 8
    public function removeIndexByName($name)
504 4
    {
505 4
        $this->getAdapter()->dropIndexByName($this->getName(), $name);
506 8
        return $this;
507 8
    }
508
509
    /**
510 8
     * Checks to see if an index exists.
511
     *
512 8
     * @param string|array $columns Columns
513 8
     * @param array        $options Options
0 ignored issues
show
Bug introduced by
There is no parameter named $options. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
514 8
     * @return boolean
515 8
     */
516
    public function hasIndex($columns)
517 8
    {
518
        return $this->getAdapter()->hasIndex($this->getName(), $columns);
519
    }
520
521
    /**
522
     * Add a foreign key to a database table.
523
     *
524
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
525
     * on_update, constraint = constraint name.
526
     *
527 1
     * @param string|array $columns Columns
528
     * @param string|Table $referencedTable   Referenced Table
529 1
     * @param string|array $referencedColumns Referenced Columns
530 1
     * @param array $options Options
531 1
     * @return Table
532 1
     */
533
    public function addForeignKey($columns, $referencedTable, $referencedColumns = ['id'], $options = [])
534
    {
535 1
        if (is_string($referencedColumns)) {
536
            $referencedColumns = [$referencedColumns]; // str to array
537
        }
538 1
        $fk = new ForeignKey();
539
        if ($referencedTable instanceof Table) {
540
            $fk->setReferencedTable($referencedTable);
541
        } else {
542
            $fk->setReferencedTable(new Table($referencedTable, [], $this->adapter));
543
        }
544
        $fk->setColumns($columns)
545
           ->setReferencedColumns($referencedColumns)
546
           ->setOptions($options);
547
        $this->foreignKeys[] = $fk;
548 1
549
        return $this;
550 1
    }
551
552
    /**
553
     * Removes the given foreign key from the table.
554
     *
555
     * @param string|array $columns    Column(s)
556
     * @param null|string  $constraint Constraint names
557
     * @return Table
558
     */
559
    public function dropForeignKey($columns, $constraint = null)
560
    {
561 15
        if (is_string($columns)) {
562
            $columns = [$columns];
563 15
        }
564 15
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
565 15
            $this->getAdapter()->dropForeignKey($this->getName(), [], $constraint);
566 15
        } else {
567
            $this->getAdapter()->dropForeignKey($this->getName(), $columns);
568 15
        }
569 15
570 15
        return $this;
571
    }
572 15
573
    /**
574 15
     * Checks to see if a foreign key exists.
575
     *
576
     * @param  string|array $columns    Column(s)
577
     * @param  null|string  $constraint Constraint names
578
     * @return boolean
579
     */
580
    public function hasForeignKey($columns, $constraint = null)
581
    {
582
        return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint);
0 ignored issues
show
Documentation introduced by
$columns is of type string|array, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
583
    }
584
585
    /**
586
     * Add timestamp columns created_at and updated_at to the table.
587
     *
588
     * @param string $createdAtColumnName
589 17
     * @param string $updatedAtColumnName
590
     *
591
     * @return Table
592 17
     */
593 11
    public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at')
594 11
    {
595 11
        $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName;
596 11
        $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName;
597
        $this->addColumn($createdAtColumnName, 'timestamp', [
598 8
                'default' => 'CURRENT_TIMESTAMP',
599 8
                'update' => ''
600
            ])
601
             ->addColumn($updatedAtColumnName, 'timestamp', [
602
                'null'    => true,
603
                'default' => null
604
             ]);
605
606
        return $this;
607 196
    }
608
609 196
    /**
610 196
     * Insert data into the table.
611 196
     *
612 196
     * @param $data array of data in the form:
613
     *              array(
614
     *                  array("col1" => "value1", "col2" => "anotherValue1"),
615
     *                  array("col2" => "value2", "col2" => "anotherValue2"),
616
     *              )
617
     *              or array("col1" => "value1", "col2" => "anotherValue1")
618
     *
619
     * @return Table
620 46
     */
621
    public function insert($data)
622 46
    {
623
        // handle array of array situations
624
        if (isset($data[0]) && is_array($data[0])) {
625
            foreach ($data as $row) {
626
                $this->data[] = $row;
627 46
            }
628 38
            return $this;
629 46
        }
630
        $this->data[] = $data;
631 46
        return $this;
632 6
    }
633 46
634
    /**
635 46
     * Creates a table from the object instance.
636 3
     *
637 46
     * @return void
638
     */
639 46
    public function create()
640 46
    {
641 46
        $this->getAdapter()->createTable($this);
642
        $this->saveData();
643
        $this->reset(); // reset pending changes
644
    }
645
646
    /**
647
     * Updates a table from the object instance.
648 196
     *
649
     * @throws \RuntimeException
650 196
     * @return void
651 196
     */
652 192
    public function update()
653
    {
654
        if (!$this->exists()) {
655 12
            throw new \RuntimeException('Cannot update a table that doesn\'t exist!');
656 12
        }
657 12
658 12
        // update table
659 12
        foreach ($this->getPendingColumns() as $column) {
660 12
            $this->getAdapter()->addColumn($this, $column);
661 1
        }
662 1
663
        foreach ($this->getIndexes() as $index) {
664 12
            $this->getAdapter()->addIndex($this, $index);
665
        }
666 12
667 11
        foreach ($this->getForeignKeys() as $foreignKey) {
668 11
            $this->getAdapter()->addForeignKey($this, $foreignKey);
669 1
        }
670 1
671 1
        $this->saveData();
672
        $this->reset(); // reset pending changes
673 12
    }
674
675
    /**
676
     * Commit the pending data waiting for insertion.
677
     *
678
     * @return void
679
     */
680 2
    public function saveData()
681
    {
682 2
        $rows = $this->getData();
683 2
        if (empty($rows)) {
684
            return;
685
        }
686
687
        $bulk = true;
688
        $row = current($rows);
689
        $c = array_keys($row);
690
        foreach ($this->getData() as $row) {
691
            $k = array_keys($row);
692 195
            if ($k != $c) {
693
                $bulk = false;
694 195
                break;
695 45
            }
696 45
        }
697 195
698
        if ($bulk) {
699
            $this->getAdapter()->bulkinsert($this, $this->getData());
700 195
        } else {
701 195
            foreach ($this->getData() as $row) {
702
                $this->getAdapter()->insert($this, $row);
703
            }
704
        }
705
    }
706
707
    /**
708
     * Truncates the table.
709
     *
710
     * @return void
711
     */
712
    public function truncate()
713
    {
714
        $this->getAdapter()->truncateTable($this->getName());
715
    }
716
717
    /**
718
     * Commits the table changes.
719
     *
720
     * If the table doesn't exist it is created otherwise it is updated.
721
     *
722
     * @return void
723
     */
724
    public function save()
725
    {
726
        if ($this->exists()) {
727
            $this->update(); // update the table
728
        } else {
729
            $this->create(); // create the table
730
        }
731
732
        $this->reset(); // reset pending changes
733
    }
734
}
735