Completed
Pull Request — master (#1175)
by
unknown
02:05
created

Table::addCustomColumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 8
Ratio 53.33 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 8
loc 15
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 3
crap 2
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
    }
206
207
    /**
208
     * Gets an array of the table columns.
209
     *
210
     * @return Column[]
211 10
     */
212
    public function getColumns()
213 10
    {
214
        return $this->getAdapter()->getColumns($this->getName());
215
    }
216
217
    /**
218
     * Sets an array of columns waiting to be committed.
219
     *
220
     * @param array $columns Columns
221
     * @return Table
222 196
     */
223
    public function setPendingColumns($columns)
224 196
    {
225 196
        $this->columns = $columns;
226
        return $this;
227
    }
228
229
    /**
230
     * Gets an array of columns waiting to be committed.
231
     *
232
     * @return Column[]
233 204
     */
234
    public function getPendingColumns()
235 204
    {
236
        return $this->columns;
237
    }
238
239
    /**
240
     * Sets an array of columns waiting to be indexed.
241
     *
242
     * @param array $indexes Indexes
243
     * @return Table
244 196
     */
245
    public function setIndexes($indexes)
246 196
    {
247 196
        $this->indexes = $indexes;
248
        return $this;
249
    }
250
251
    /**
252
     * Gets an array of indexes waiting to be committed.
253
     *
254
     * @return array
255 191
     */
256
    public function getIndexes()
257 191
    {
258
        return $this->indexes;
259
    }
260
261
    /**
262
     * Sets an array of foreign keys waiting to be commited.
263
     *
264
     * @param ForeignKey[] $foreignKeys foreign keys
265
     * @return Table
266 196
     */
267
    public function setForeignKeys($foreignKeys)
268 196
    {
269 196
        $this->foreignKeys = $foreignKeys;
270
        return $this;
271
    }
272
273
    /**
274
     * Gets an array of foreign keys waiting to be commited.
275
     *
276
     * @return array|ForeignKey[]
277 192
     */
278
    public function getForeignKeys()
279 192
    {
280
        return $this->foreignKeys;
281
    }
282
283
    /**
284
     * Sets an array of data to be inserted.
285
     *
286
     * @param array $data Data
287
     * @return Table
288 196
     */
289
    public function setData($data)
290 196
    {
291 196
        $this->data = $data;
292
        return $this;
293
    }
294
295
    /**
296
     * Gets the data waiting to be inserted.
297
     *
298
     * @return array
299 197
     */
300
    public function getData()
301 197
    {
302
        return $this->data;
303
    }
304
305
    /**
306
     * Resets all of the pending table changes.
307
     *
308
     * @return void
309 196
     */
310
    public function reset()
311 196
    {
312 196
        $this->setPendingColumns([]);
313 196
        $this->setIndexes([]);
314 196
        $this->setForeignKeys([]);
315 196
        $this->setData([]);
316
    }
317
318
    /**
319
     * Add a table column.
320
     *
321
     * Type can be: string, text, integer, float, decimal, datetime, timestamp,
322
     * time, date, binary, boolean.
323
     *
324
     * Valid options can be: limit, default, null, precision or scale.
325
     *
326
     * @param string|Column $columnName Column Name
327
     * @param string $type Column Type
328
     * @param array $options Column Options
329
     * @throws \RuntimeException
330
     * @throws \InvalidArgumentException
331
     * @return Table
332 210
     */
333
    public function addColumn($columnName, $type = null, $options = [])
334
    {
335 210
        // we need an adapter set to add a column
336 1
        if ($this->getAdapter() === null) {
337
            throw new \RuntimeException('An adapter must be specified to add a column.');
338
        }
339
340 209
        // create a new column object if only strings were supplied
341 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...
342 207
            $column = new Column();
343 207
            $column->setName($columnName);
344 207
            $column->setType($type);
345 207
            $column->setOptions($options); // map options to column methods
346 2
        } else {
347
            $column = $columnName;
348
        }
349
350 209
        // Delegate to Adapters to check column type
351 1
        if (!$this->getAdapter()->isValidColumnType($column)) {
352 1
            throw new \InvalidArgumentException(sprintf(
353 1
                'An invalid column type "%s" was specified for column "%s".',
354 1
                $column->getType(),
355 1
                $column->getName()
356
            ));
357
        }
358 208
359 208
        $this->columns[] = $column;
360
        return $this;
361
    }
362
363
    /**
364
     * Add a table column with a custom type definition.
365
     *
366
     * Type can be any string that you expect your DBMS to understand
367
     *
368 1
     * Valid options can be:  default or null.
369
     *
370 1
     * For widely supported types @see addColumn
371 1
     *
372
     * @param string|CustomColumn $columnName Column Name
373
     * @param string $type Column Type
374
     * @param array $options Column Options
375
     * @return Table
376
     */
377
    public function addCustomColumn($columnName, $type = null, $options = array())
378
    {
379
        // create a new CustomColumn object if only strings were supplied
380 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...
381 4
            $column = new CustomColumn();
382
            $column->setName($columnName);
383 4
            $column->setType($type);
384 4
            $column->setOptions($options); // map options to column methods
385
        } else {
386
            $column = $columnName;
387
        }
388
389
        $this->columns[] = $column;
390
        return $this;
391
    }
392
393
    /**
394
     * Remove a table column.
395 17
     *
396
     * @param string $columnName Column Name
397
     * @return Table
398 17
     */
399 4
    public function removeColumn($columnName)
400 4
    {
401 4
        $this->getAdapter()->dropColumn($this->getName(), $columnName);
402 4
        return $this;
403 13
    }
404
405
    /**
406
     * Rename a table column.
407 17
     *
408 15
     * @param string $oldName Old Column Name
409 15
     * @param string $newName New Column Name
410
     * @return Table
411 17
     */
412 17
    public function renameColumn($oldName, $newName)
413
    {
414
        $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName);
415
        return $this;
416
    }
417
418
    /**
419
     * Change a table column type.
420
     *
421 89
     * @param string        $columnName    Column Name
422
     * @param string|Column $newColumnType New Column Type
423 89
     * @param array         $options       Options
424
     * @return Table
425
     */
426
    public function changeColumn($columnName, $newColumnType, $options = [])
427
    {
428
        // create a column object if one wasn't supplied
429
        if (!$newColumnType instanceof Column) {
430
            $newColumn = new Column();
431
            $newColumn->setType($newColumnType);
432
            $newColumn->setOptions($options);
433
        } else {
434
            $newColumn = $newColumnType;
435 29
        }
436
437
        // if the name was omitted use the existing column name
438 29
        if ($newColumn->getName() === null || strlen($newColumn->getName()) === 0) {
439 28
            $newColumn->setName($columnName);
440 28
        }
441 22
442 22
        $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn);
443 28
        return $this;
444 28
    }
445 28
446 1
    /**
447
     * Checks to see if a column exists.
448
     *
449 29
     * @param string $columnName Column Name
450 29
     * @return boolean
451
     */
452
    public function hasColumn($columnName)
453
    {
454
        return $this->getAdapter()->hasColumn($this->getName(), $columnName);
455
    }
456
457
    /**
458
     * Add an index to a database table.
459 1
     *
460
     * In $options you can specific unique = true/false or name (index name).
461 1
     *
462 1
     * @param string|array|Index $columns Table Column(s)
463
     * @param array $options Index Options
464
     * @return Table
465
     */
466
    public function addIndex($columns, $options = [])
467
    {
468
        // create a new index object if strings or an array of strings were supplied
469
        if (!$columns instanceof Index) {
470
            $index = new Index();
471 1
            if (is_string($columns)) {
472
                $columns = [$columns]; // str to array
473 1
            }
474 1
            $index->setColumns($columns);
475
            $index->setOptions($options);
476
        } else {
477
            $index = $columns;
478
        }
479
480
        $this->indexes[] = $index;
481
        return $this;
482
    }
483
484 12
    /**
485
     * Removes the given index from a table.
486 12
     *
487
     * @param array $columns Columns
488
     * @return Table
489
     */
490
    public function removeIndex($columns)
491
    {
492
        $this->getAdapter()->dropIndex($this->getName(), $columns);
493
        return $this;
494
    }
495
496
    /**
497
     * Removes the given index identified by its name from a table.
498
     *
499
     * @param string $name Index name
500
     * @return Table
501 8
     */
502
    public function removeIndexByName($name)
503 8
    {
504 4
        $this->getAdapter()->dropIndexByName($this->getName(), $name);
505 4
        return $this;
506 8
    }
507 8
508
    /**
509
     * Checks to see if an index exists.
510 8
     *
511
     * @param string|array $columns Columns
512 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...
513 8
     * @return boolean
514 8
     */
515 8
    public function hasIndex($columns)
516
    {
517 8
        return $this->getAdapter()->hasIndex($this->getName(), $columns);
518
    }
519
520
    /**
521
     * Add a foreign key to a database table.
522
     *
523
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
524
     * on_update, constraint = constraint name.
525
     *
526
     * @param string|array $columns Columns
527 1
     * @param string|Table $referencedTable   Referenced Table
528
     * @param string|array $referencedColumns Referenced Columns
529 1
     * @param array $options Options
530 1
     * @return Table
531 1
     */
532 1
    public function addForeignKey($columns, $referencedTable, $referencedColumns = ['id'], $options = [])
533
    {
534
        if (is_string($referencedColumns)) {
535 1
            $referencedColumns = [$referencedColumns]; // str to array
536
        }
537
        $fk = new ForeignKey();
538 1
        if ($referencedTable instanceof Table) {
539
            $fk->setReferencedTable($referencedTable);
540
        } else {
541
            $fk->setReferencedTable(new Table($referencedTable, [], $this->adapter));
542
        }
543
        $fk->setColumns($columns)
544
           ->setReferencedColumns($referencedColumns)
545
           ->setOptions($options);
546
        $this->foreignKeys[] = $fk;
547
548 1
        return $this;
549
    }
550 1
551
    /**
552
     * Removes the given foreign key from the table.
553
     *
554
     * @param string|array $columns    Column(s)
555
     * @param null|string  $constraint Constraint names
556
     * @return Table
557
     */
558
    public function dropForeignKey($columns, $constraint = null)
559
    {
560
        if (is_string($columns)) {
561 15
            $columns = [$columns];
562
        }
563 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...
564 15
            $this->getAdapter()->dropForeignKey($this->getName(), [], $constraint);
565 15
        } else {
566 15
            $this->getAdapter()->dropForeignKey($this->getName(), $columns);
567
        }
568 15
569 15
        return $this;
570 15
    }
571
572 15
    /**
573
     * Checks to see if a foreign key exists.
574 15
     *
575
     * @param  string|array $columns    Column(s)
576
     * @param  null|string  $constraint Constraint names
577
     * @return boolean
578
     */
579
    public function hasForeignKey($columns, $constraint = null)
580
    {
581
        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...
582
    }
583
584
    /**
585
     * Add timestamp columns created_at and updated_at to the table.
586
     *
587
     * @param string $createdAtColumnName
588
     * @param string $updatedAtColumnName
589 17
     *
590
     * @return Table
591
     */
592 17
    public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at')
593 11
    {
594 11
        $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName;
595 11
        $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName;
596 11
        $this->addColumn($createdAtColumnName, 'timestamp', [
597
                'default' => 'CURRENT_TIMESTAMP',
598 8
                'update' => ''
599 8
            ])
600
             ->addColumn($updatedAtColumnName, 'timestamp', [
601
                'null'    => true,
602
                'default' => null
603
             ]);
604
605
        return $this;
606
    }
607 196
608
    /**
609 196
     * Insert data into the table.
610 196
     *
611 196
     * @param $data array of data in the form:
612 196
     *              array(
613
     *                  array("col1" => "value1", "col2" => "anotherValue1"),
614
     *                  array("col2" => "value2", "col2" => "anotherValue2"),
615
     *              )
616
     *              or array("col1" => "value1", "col2" => "anotherValue1")
617
     *
618
     * @return Table
619
     */
620 46
    public function insert($data)
621
    {
622 46
        // handle array of array situations
623
        if (isset($data[0]) && is_array($data[0])) {
624
            foreach ($data as $row) {
625
                $this->data[] = $row;
626
            }
627 46
            return $this;
628 38
        }
629 46
        $this->data[] = $data;
630
        return $this;
631 46
    }
632 6
633 46
    /**
634
     * Creates a table from the object instance.
635 46
     *
636 3
     * @return void
637 46
     */
638
    public function create()
639 46
    {
640 46
        $this->getAdapter()->createTable($this);
641 46
        $this->saveData();
642
        $this->reset(); // reset pending changes
643
    }
644
645
    /**
646
     * Updates a table from the object instance.
647
     *
648 196
     * @throws \RuntimeException
649
     * @return void
650 196
     */
651 196
    public function update()
652 192
    {
653
        if (!$this->exists()) {
654
            throw new \RuntimeException('Cannot update a table that doesn\'t exist!');
655 12
        }
656 12
657 12
        // update table
658 12
        foreach ($this->getPendingColumns() as $column) {
659 12
            $this->getAdapter()->addColumn($this, $column);
660 12
        }
661 1
662 1
        foreach ($this->getIndexes() as $index) {
663
            $this->getAdapter()->addIndex($this, $index);
664 12
        }
665
666 12
        foreach ($this->getForeignKeys() as $foreignKey) {
667 11
            $this->getAdapter()->addForeignKey($this, $foreignKey);
668 11
        }
669 1
670 1
        $this->saveData();
671 1
        $this->reset(); // reset pending changes
672
    }
673 12
674
    /**
675
     * Commit the pending data waiting for insertion.
676
     *
677
     * @return void
678
     */
679
    public function saveData()
680 2
    {
681
        $rows = $this->getData();
682 2
        if (empty($rows)) {
683 2
            return;
684
        }
685
686
        $bulk = true;
687
        $row = current($rows);
688
        $c = array_keys($row);
689
        foreach ($this->getData() as $row) {
690
            $k = array_keys($row);
691
            if ($k != $c) {
692 195
                $bulk = false;
693
                break;
694 195
            }
695 45
        }
696 45
697 195
        if ($bulk) {
698
            $this->getAdapter()->bulkinsert($this, $this->getData());
699
        } else {
700 195
            foreach ($this->getData() as $row) {
701 195
                $this->getAdapter()->insert($this, $row);
702
            }
703
        }
704
    }
705
706
    /**
707
     * Truncates the table.
708
     *
709
     * @return void
710
     */
711
    public function truncate()
712
    {
713
        $this->getAdapter()->truncateTable($this->getName());
714
    }
715
716
    /**
717
     * Commits the table changes.
718
     *
719
     * If the table doesn't exist it is created otherwise it is updated.
720
     *
721
     * @return void
722
     */
723
    public function save()
724
    {
725
        if ($this->exists()) {
726
            $this->update(); // update the table
727
        } else {
728
            $this->create(); // create the table
729
        }
730
731
        $this->reset(); // reset pending changes
732
    }
733
}
734