Table::addForeignKey()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 4
crap 1
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Db;
9
10
use InvalidArgumentException;
11
use Phinx\Db\Action\AddColumn;
12
use Phinx\Db\Action\AddForeignKey;
13
use Phinx\Db\Action\AddIndex;
14
use Phinx\Db\Action\ChangeColumn;
15
use Phinx\Db\Action\ChangeComment;
16
use Phinx\Db\Action\ChangePrimaryKey;
17
use Phinx\Db\Action\CreateTable;
18
use Phinx\Db\Action\DropForeignKey;
19
use Phinx\Db\Action\DropIndex;
20
use Phinx\Db\Action\DropTable;
21
use Phinx\Db\Action\RemoveColumn;
22
use Phinx\Db\Action\RenameColumn;
23
use Phinx\Db\Action\RenameTable;
24
use Phinx\Db\Adapter\AdapterInterface;
25
use Phinx\Db\Plan\Intent;
26
use Phinx\Db\Plan\Plan;
27
use Phinx\Db\Table\Column;
28
use Phinx\Db\Table\Table as TableValue;
29
use RuntimeException;
30
31
/**
32
 * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
33
 */
34
class Table
35
{
36
    /**
37
     * @var \Phinx\Db\Table\Table
38
     */
39
    protected $table;
40
41
    /**
42
     * @var \Phinx\Db\Adapter\AdapterInterface|null
43
     */
44
    protected $adapter;
45
46
    /**
47
     * @var \Phinx\Db\Plan\Intent
48
     */
49
    protected $actions;
50
51
    /**
52
     * @var array
53
     */
54
    protected $data = [];
55
56
    /**
57
     * @param string $name Table Name
58
     * @param array $options Options
59
     * @param \Phinx\Db\Adapter\AdapterInterface|null $adapter Database Adapter
60
     */
61
    public function __construct($name, $options = [], ?AdapterInterface $adapter = null)
62
    {
63
        $this->table = new TableValue($name, $options);
64
        $this->actions = new Intent();
65
66
        if ($adapter !== null) {
67
            $this->setAdapter($adapter);
68
        }
69
    }
70
71
    /**
72
     * Gets the table name.
73
     *
74
     * @return string|null
75
     */
76
    public function getName()
77
    {
78
        return $this->table->getName();
79
    }
80
81
    /**
82
     * Gets the table options.
83
     *
84 239
     * @return array
85
     */
86 239
    public function getOptions()
87 239
    {
88
        return $this->table->getOptions();
89 239
    }
90 231
91 231
    /**
92 239
     * Gets the table name and options as an object
93
     *
94
     * @return \Phinx\Db\Table\Table
95
     */
96
    public function getTable()
97
    {
98
        return $this->table;
99
    }
100 239
101
    /**
102 239
     * Sets the database adapter.
103 239
     *
104
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
105
     * @return $this
106
     */
107
    public function setAdapter(AdapterInterface $adapter)
108
    {
109
        $this->adapter = $adapter;
110
111 215
        return $this;
112
    }
113 215
114
    /**
115
     * Gets the database adapter.
116
     *
117
     * @throws \RuntimeException
118
     * @return \Phinx\Db\Adapter\AdapterInterface|null
119
     */
120
    public function getAdapter()
121
    {
122 239
        if (!$this->adapter) {
123
            throw new RuntimeException('There is no database adapter set yet, cannot proceed');
124 239
        }
125 239
126
        return $this->adapter;
127
    }
128
129
    /**
130
     * Does the table have pending actions?
131
     *
132
     * @return bool
133 189
     */
134
    public function hasPendingActions()
135 189
    {
136
        return count($this->actions->getActions()) > 0 || count($this->data) > 0;
137
    }
138
139
    /**
140
     * Does the table exist?
141
     *
142
     * @return bool
143
     */
144 231
    public function exists()
145
    {
146 231
        return $this->getAdapter()->hasTable($this->getName());
147 231
    }
148
149
    /**
150
     * Drops the database table.
151
     *
152
     * @return $this
153
     */
154
    public function drop()
155 225
    {
156
        $this->actions->addAction(new DropTable($this->table));
157 225
158
        return $this;
159
    }
160
161
    /**
162
     * Renames the database table.
163
     *
164
     * @param string $newTableName New Table Name
165 195
     * @return $this
166
     */
167 195
    public function rename($newTableName)
168
    {
169
        $this->actions->addAction(new RenameTable($this->table, $newTableName));
170
171
        return $this;
172
    }
173
174
    /**
175 1
     * Changes the primary key of the database table.
176
     *
177 1
     * @param string|string[]|null $columns Column name(s) to belong to the primary key, or null to drop the key
178 1
     * @return $this
179
     */
180
    public function changePrimaryKey($columns)
181
    {
182
        $this->actions->addAction(new ChangePrimaryKey($this->table, $columns));
183
184
        return $this;
185
    }
186 3
187
    /**
188 3
     * Checks to see if a primary key exists.
189 3
     *
190 3
     * @param string|string[] $columns Column(s)
191
     * @param string|null $constraint Constraint names
192
     * @return bool
193
     */
194
    public function hasPrimaryKey($columns, $constraint = null)
195
    {
196
        return $this->getAdapter()->hasPrimaryKey($this->getName(), $columns, $constraint);
197
    }
198
199
    /**
200
     * Changes the comment of the database table.
201
     *
202
     * @param string|null $comment New comment string, or null to drop the comment
203
     * @return $this
204
     */
205
    public function changeComment($comment)
206
    {
207
        $this->actions->addAction(new ChangeComment($this->table, $comment));
208
209
        return $this;
210
    }
211 10
212
    /**
213 10
     * Gets an array of the table columns.
214
     *
215
     * @return \Phinx\Db\Table\Column[]
216
     */
217
    public function getColumns()
218
    {
219
        return $this->getAdapter()->getColumns($this->getName());
220
    }
221
222 196
    /**
223
     * Gets a table column if it exists.
224 196
     *
225 196
     * @param string $name Column name
226
     * @return \Phinx\Db\Table\Column|null
227
     */
228
    public function getColumn($name)
229
    {
230
        $columns = array_filter(
231
            $this->getColumns(),
232
            function ($column) use ($name) {
233 204
                return $column->getName() === $name;
234
            }
235 204
        );
236
237
        return array_pop($columns);
238
    }
239
240
    /**
241
     * Sets an array of data to be inserted.
242
     *
243
     * @param array $data Data
244 196
     * @return $this
245
     */
246 196
    public function setData($data)
247 196
    {
248
        $this->data = $data;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Gets the data waiting to be inserted.
255 191
     *
256
     * @return array
257 191
     */
258
    public function getData()
259
    {
260
        return $this->data;
261
    }
262
263
    /**
264
     * Resets all of the pending data to be inserted
265
     *
266 196
     * @return void
267
     */
268 196
    public function resetData()
269 196
    {
270
        $this->setData([]);
271
    }
272
273
    /**
274
     * Resets all of the pending table changes.
275
     *
276
     * @return void
277 192
     */
278
    public function reset()
279 192
    {
280
        $this->actions = new Intent();
281
        $this->resetData();
282
    }
283
284
    /**
285
     * Add a table column.
286
     *
287
     * Type can be: string, text, integer, float, decimal, datetime, timestamp,
288 196
     * time, date, binary, boolean.
289
     *
290 196
     * Valid options can be: limit, default, null, precision or scale.
291 196
     *
292
     * @param string|\Phinx\Db\Table\Column $columnName Column Name
293
     * @param string|\Phinx\Util\Literal|null $type Column Type
294
     * @param array $options Column Options
295
     * @throws \InvalidArgumentException
296
     * @return $this
297
     */
298
    public function addColumn($columnName, $type = null, $options = [])
299 197
    {
300
        if ($columnName instanceof Column) {
301 197
            $action = new AddColumn($this->table, $columnName);
302
        } else {
303
            $action = AddColumn::build($this->table, $columnName, $type, $options);
304
        }
305
306
        // Delegate to Adapters to check column type
307
        if (!$this->getAdapter()->isValidColumnType($action->getColumn())) {
308
            throw new InvalidArgumentException(sprintf(
309 196
                'An invalid column type "%s" was specified for column "%s".',
310
                $type,
311 196
                $action->getColumn()->getName()
312 196
            ));
313 196
        }
314 196
315 196
        $this->actions->addAction($action);
316
317
        return $this;
318
    }
319
320
    /**
321
     * Remove a table column.
322
     *
323
     * @param string $columnName Column Name
324
     * @return $this
325
     */
326
    public function removeColumn($columnName)
327
    {
328
        $action = RemoveColumn::build($this->table, $columnName);
329
        $this->actions->addAction($action);
330
331
        return $this;
332 210
    }
333
334
    /**
335 210
     * Rename a table column.
336 1
     *
337
     * @param string $oldName Old Column Name
338
     * @param string $newName New Column Name
339
     * @return $this
340 209
     */
341 207
    public function renameColumn($oldName, $newName)
342 207
    {
343 207
        $action = RenameColumn::build($this->table, $oldName, $newName);
344 207
        $this->actions->addAction($action);
345 207
346 2
        return $this;
347
    }
348
349
    /**
350 209
     * Change a table column type.
351 1
     *
352 1
     * @param string $columnName Column Name
353 1
     * @param string|\Phinx\Db\Table\Column|\Phinx\Util\Literal $newColumnType New Column Type
354 1
     * @param array $options Options
355 1
     * @return $this
356
     */
357
    public function changeColumn($columnName, $newColumnType, array $options = [])
358 208
    {
359 208
        if ($newColumnType instanceof Column) {
360
            $action = new ChangeColumn($this->table, $columnName, $newColumnType);
361
        } else {
362
            $action = ChangeColumn::build($this->table, $columnName, $newColumnType, $options);
363
        }
364
        $this->actions->addAction($action);
365
366
        return $this;
367
    }
368 1
369
    /**
370 1
     * Checks to see if a column exists.
371 1
     *
372
     * @param string $columnName Column Name
373
     * @return bool
374
     */
375
    public function hasColumn($columnName)
376
    {
377
        return $this->getAdapter()->hasColumn($this->getName(), $columnName);
378
    }
379
380
    /**
381 4
     * Add an index to a database table.
382
     *
383 4
     * In $options you can specify unique = true/false, and name (index name).
384 4
     *
385
     * @param string|array|\Phinx\Db\Table\Index $columns Table Column(s)
386
     * @param array $options Index Options
387
     * @return $this
388
     */
389
    public function addIndex($columns, array $options = [])
390
    {
391
        $action = AddIndex::build($this->table, $columns, $options);
392
        $this->actions->addAction($action);
393
394
        return $this;
395 17
    }
396
397
    /**
398 17
     * Removes the given index from a table.
399 4
     *
400 4
     * @param string|string[] $columns Columns
401 4
     * @return $this
402 4
     */
403 13
    public function removeIndex($columns)
404
    {
405
        $action = DropIndex::build($this->table, is_string($columns) ? [$columns] : $columns);
406
        $this->actions->addAction($action);
407 17
408 15
        return $this;
409 15
    }
410
411 17
    /**
412 17
     * Removes the given index identified by its name from a table.
413
     *
414
     * @param string $name Index name
415
     * @return $this
416
     */
417
    public function removeIndexByName($name)
418
    {
419
        $action = DropIndex::buildFromName($this->table, $name);
420
        $this->actions->addAction($action);
421 89
422
        return $this;
423 89
    }
424
425
    /**
426
     * Checks to see if an index exists.
427
     *
428
     * @param string|string[] $columns Columns
429
     * @return bool
430
     */
431
    public function hasIndex($columns)
432
    {
433
        return $this->getAdapter()->hasIndex($this->getName(), $columns);
434
    }
435 29
436
    /**
437
     * Checks to see if an index specified by name exists.
438 29
     *
439 28
     * @param string $indexName Index name
440 28
     * @return bool
441 22
     */
442 22
    public function hasIndexByName($indexName)
443 28
    {
444 28
        return $this->getAdapter()->hasIndexByName($this->getName(), $indexName);
445 28
    }
446 1
447
    /**
448
     * Add a foreign key to a database table.
449 29
     *
450 29
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
451
     * on_update, constraint = constraint name.
452
     *
453
     * @param string|string[] $columns Columns
454
     * @param string|\Phinx\Db\Table\Table $referencedTable Referenced Table
455
     * @param string|string[] $referencedColumns Referenced Columns
456
     * @param array $options Options
457
     * @return $this
458
     */
459 1
    public function addForeignKey($columns, $referencedTable, $referencedColumns = ['id'], $options = [])
460
    {
461 1
        $action = AddForeignKey::build($this->table, $columns, $referencedTable, $referencedColumns, $options);
462 1
        $this->actions->addAction($action);
463
464
        return $this;
465
    }
466
467
    /**
468
     * Add a foreign key to a database table with a given name.
469
     *
470
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
471 1
     * on_update, constraint = constraint name.
472
     *
473 1
     * @param string $name The constraint name
474 1
     * @param string|string[] $columns Columns
475
     * @param string|\Phinx\Db\Table\Table $referencedTable Referenced Table
476
     * @param string|string[] $referencedColumns Referenced Columns
477
     * @param array $options Options
478
     * @return $this
479
     */
480
    public function addForeignKeyWithName($name, $columns, $referencedTable, $referencedColumns = ['id'], $options = [])
481
    {
482
        $action = AddForeignKey::build(
483
            $this->table,
484 12
            $columns,
485
            $referencedTable,
486 12
            $referencedColumns,
487
            $options,
488
            $name
489
        );
490
        $this->actions->addAction($action);
491
492
        return $this;
493
    }
494
495
    /**
496
     * Removes the given foreign key from the table.
497
     *
498
     * @param string|string[] $columns Column(s)
499
     * @param string|null $constraint Constraint names
500
     * @return $this
501 8
     */
502
    public function dropForeignKey($columns, $constraint = null)
503 8
    {
504 4
        $action = DropForeignKey::build($this->table, $columns, $constraint);
505 4
        $this->actions->addAction($action);
506 8
507 8
        return $this;
508
    }
509
510 8
    /**
511
     * Checks to see if a foreign key exists.
512 8
     *
513 8
     * @param string|string[] $columns Column(s)
514 8
     * @param string|null $constraint Constraint names
515 8
     * @return bool
516
     */
517 8
    public function hasForeignKey($columns, $constraint = null)
518
    {
519
        return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint);
520
    }
521
522
    /**
523
     * Add timestamp columns created_at and updated_at to the table.
524
     *
525
     * @param string|false|null $createdAt Alternate name for the created_at column
526
     * @param string|false|null $updatedAt Alternate name for the updated_at column
527 1
     * @param bool $withTimezone Whether to set the timezone option on the added columns
528
     * @return $this
529 1
     */
530 1
    public function addTimestamps($createdAt = 'created_at', $updatedAt = 'updated_at', $withTimezone = false)
531 1
    {
532 1
        $createdAt = $createdAt ?? 'created_at';
533
        $updatedAt = $updatedAt ?? 'updated_at';
534
535 1
        if (!$createdAt && !$updatedAt) {
536
            throw new \RuntimeException('Cannot set both created_at and updated_at columns to false');
537
        }
538 1
539
        if ($createdAt) {
540
            $this->addColumn($createdAt, 'timestamp', [
541
                'default' => 'CURRENT_TIMESTAMP',
542
                'update' => '',
543
                'timezone' => $withTimezone,
544
            ]);
545
        }
546
        if ($updatedAt) {
547
            $this->addColumn($updatedAt, 'timestamp', [
548 1
                'null' => true,
549
                'default' => null,
550 1
                'update' => 'CURRENT_TIMESTAMP',
551
                'timezone' => $withTimezone,
552
            ]);
553
        }
554
555
        return $this;
556
    }
557
558
    /**
559
     * Alias that always sets $withTimezone to true
560
     *
561 15
     * @see addTimestamps
562
     * @param string|null $createdAt Alternate name for the created_at column
563 15
     * @param string|null $updatedAt Alternate name for the updated_at column
564 15
     * @return $this
565 15
     */
566 15
    public function addTimestampsWithTimezone($createdAt = null, $updatedAt = null)
567
    {
568 15
        $this->addTimestamps($createdAt, $updatedAt, true);
569 15
570 15
        return $this;
571
    }
572 15
573
    /**
574 15
     * Insert data into the table.
575
     *
576
     * @param array $data array of data in the form:
577
     *              array(
578
     *                  array("col1" => "value1", "col2" => "anotherValue1"),
579
     *                  array("col2" => "value2", "col2" => "anotherValue2"),
580
     *              )
581
     *              or array("col1" => "value1", "col2" => "anotherValue1")
582
     * @return $this
583
     */
584
    public function insert($data)
585
    {
586
        // handle array of array situations
587
        $keys = array_keys($data);
588
        $firstKey = array_shift($keys);
589 17
        if ($firstKey !== null && is_array($data[$firstKey])) {
590
            foreach ($data as $row) {
591
                $this->data[] = $row;
592 17
            }
593 11
594 11
            return $this;
595 11
        }
596 11
597
        if (count($data) > 0) {
598 8
            $this->data[] = $data;
599 8
        }
600
601
        return $this;
602
    }
603
604
    /**
605
     * Creates a table from the object instance.
606
     *
607 196
     * @return void
608
     */
609 196
    public function create()
610 196
    {
611 196
        $this->executeActions(false);
612 196
        $this->saveData();
613
        $this->reset(); // reset pending changes
614
    }
615
616
    /**
617
     * Updates a table from the object instance.
618
     *
619
     * @return void
620 46
     */
621
    public function update()
622 46
    {
623
        $this->executeActions(true);
624
        $this->saveData();
625
        $this->reset(); // reset pending changes
626
    }
627 46
628 38
    /**
629 46
     * Commit the pending data waiting for insertion.
630
     *
631 46
     * @return void
632 6
     */
633 46
    public function saveData()
634
    {
635 46
        $rows = $this->getData();
636 3
        if (empty($rows)) {
637 46
            return;
638
        }
639 46
640 46
        $bulk = true;
641 46
        $row = current($rows);
642
        $c = array_keys($row);
643
        foreach ($this->getData() as $row) {
644
            $k = array_keys($row);
645
            if ($k != $c) {
646
                $bulk = false;
647
                break;
648 196
            }
649
        }
650 196
651 196
        if ($bulk) {
652 192
            $this->getAdapter()->bulkinsert($this->table, $this->getData());
653
        } else {
654
            foreach ($this->getData() as $row) {
655 12
                $this->getAdapter()->insert($this->table, $row);
656 12
            }
657 12
        }
658 12
659 12
        $this->resetData();
660 12
    }
661 1
662 1
    /**
663
     * Immediately truncates the table. This operation cannot be undone
664 12
     *
665
     * @return void
666 12
     */
667 11
    public function truncate()
668 11
    {
669 1
        $this->getAdapter()->truncateTable($this->getName());
670 1
    }
671 1
672
    /**
673 12
     * Commits the table changes.
674
     *
675
     * If the table doesn't exist it is created otherwise it is updated.
676
     *
677
     * @return void
678
     */
679
    public function save()
680 2
    {
681
        if ($this->exists()) {
682 2
            $this->update(); // update the table
683 2
        } else {
684
            $this->create(); // create the table
685
        }
686
    }
687
688
    /**
689
     * Executes all the pending actions for this table
690
     *
691
     * @param bool $exists Whether or not the table existed prior to executing this method
692 195
     * @return void
693
     */
694 195
    protected function executeActions($exists)
695 45
    {
696 45
        // Renaming a table is tricky, specially when running a reversible migration
697 195
        // down. We will just assume the table already exists if the user commands a
698
        // table rename.
699
        if (!$exists) {
700 195
            foreach ($this->actions->getActions() as $action) {
701 195
                if ($action instanceof RenameTable) {
702
                    $exists = true;
703
                    break;
704
                }
705
            }
706
        }
707
708
        // If the table does not exist, the last command in the chain needs to be
709
        // a CreateTable action.
710
        if (!$exists) {
711
            $this->actions->addAction(new CreateTable($this->table));
712
        }
713
714
        $plan = new Plan($this->actions);
715
        $plan->execute($this->getAdapter());
716
    }
717
}
718