Passed
Pull Request — master (#1928)
by Corey
03:52 queued 01:10
created

Table::saveData()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 27
c 0
b 0
f 0
ccs 16
cts 16
cp 1
rs 9.0777
cc 6
nc 7
nop 0
crap 6
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
     *
106
     * @return $this
107
     */
108
    public function setAdapter(AdapterInterface $adapter)
109
    {
110
        $this->adapter = $adapter;
111 215
112
        return $this;
113 215
    }
114
115
    /**
116
     * Gets the database adapter.
117
     *
118
     * @throws \RuntimeException
119
     *
120
     * @return \Phinx\Db\Adapter\AdapterInterface|null
121
     */
122 239
    public function getAdapter()
123
    {
124 239
        if (!$this->adapter) {
125 239
            throw new RuntimeException('There is no database adapter set yet, cannot proceed');
126
        }
127
128
        return $this->adapter;
129
    }
130
131
    /**
132
     * Does the table have pending actions?
133 189
     *
134
     * @return bool
135 189
     */
136
    public function hasPendingActions()
137
    {
138
        return count($this->actions->getActions()) > 0 || count($this->data) > 0;
139
    }
140
141
    /**
142
     * Does the table exist?
143
     *
144 231
     * @return bool
145
     */
146 231
    public function exists()
147 231
    {
148
        return $this->getAdapter()->hasTable($this->getName());
149
    }
150
151
    /**
152
     * Drops the database table.
153
     *
154
     * @return $this
155 225
     */
156
    public function drop()
157 225
    {
158
        $this->actions->addAction(new DropTable($this->table));
159
160
        return $this;
161
    }
162
163
    /**
164
     * Renames the database table.
165 195
     *
166
     * @param string $newTableName New Table Name
167 195
     *
168
     * @return $this
169
     */
170
    public function rename($newTableName)
171
    {
172
        $this->actions->addAction(new RenameTable($this->table, $newTableName));
173
174
        return $this;
175 1
    }
176
177 1
    /**
178 1
     * Changes the primary key of the database table.
179
     *
180
     * @param string|string[]|null $columns Column name(s) to belong to the primary key, or null to drop the key
181
     *
182
     * @return $this
183
     */
184
    public function changePrimaryKey($columns)
185
    {
186 3
        $this->actions->addAction(new ChangePrimaryKey($this->table, $columns));
187
188 3
        return $this;
189 3
    }
190 3
191
    /**
192
     * Changes the comment of the database table.
193
     *
194
     * @param string|null $comment New comment string, or null to drop the comment
195
     *
196
     * @return $this
197
     */
198
    public function changeComment($comment)
199
    {
200
        $this->actions->addAction(new ChangeComment($this->table, $comment));
201
202
        return $this;
203
    }
204
205
    /**
206
     * Gets an array of the table columns.
207
     *
208
     * @return \Phinx\Db\Table\Column[]
209
     */
210
    public function getColumns()
211 10
    {
212
        return $this->getAdapter()->getColumns($this->getName());
213 10
    }
214
215
    /**
216
     * Gets a table column if it exists.
217
     *
218
     * @param string $name Column name
219
     *
220
     * @return \Phinx\Db\Table\Column|null
221
     */
222 196
    public function getColumn($name)
223
    {
224 196
        $columns = array_filter(
225 196
            $this->getColumns(),
226
            function ($column) use ($name) {
227
                return $column->getName() === $name;
228
            }
229
        );
230
231
        return array_pop($columns);
232
    }
233 204
234
    /**
235 204
     * Sets an array of data to be inserted.
236
     *
237
     * @param array $data Data
238
     *
239
     * @return $this
240
     */
241
    public function setData($data)
242
    {
243
        $this->data = $data;
244 196
245
        return $this;
246 196
    }
247 196
248
    /**
249
     * Gets the data waiting to be inserted.
250
     *
251
     * @return array
252
     */
253
    public function getData()
254
    {
255 191
        return $this->data;
256
    }
257 191
258
    /**
259
     * Resets all of the pending data to be inserted
260
     *
261
     * @return void
262
     */
263
    public function resetData()
264
    {
265
        $this->setData([]);
266 196
    }
267
268 196
    /**
269 196
     * Resets all of the pending table changes.
270
     *
271
     * @return void
272
     */
273
    public function reset()
274
    {
275
        $this->actions = new Intent();
276
        $this->resetData();
277 192
    }
278
279 192
    /**
280
     * Add a table column.
281
     *
282
     * Type can be: string, text, integer, float, decimal, datetime, timestamp,
283
     * time, date, binary, boolean.
284
     *
285
     * Valid options can be: limit, default, null, precision or scale.
286
     *
287
     * @param string|\Phinx\Db\Table\Column $columnName Column Name
288 196
     * @param string|\Phinx\Util\Literal|null $type Column Type
289
     * @param array $options Column Options
290 196
     *
291 196
     * @throws \InvalidArgumentException
292
     *
293
     * @return $this
294
     */
295
    public function addColumn($columnName, $type = null, $options = [])
296
    {
297
        if ($columnName instanceof Column) {
298
            $action = new AddColumn($this->table, $columnName);
299 197
        } else {
300
            $action = AddColumn::build($this->table, $columnName, $type, $options);
301 197
        }
302
303
        // Delegate to Adapters to check column type
304
        if (!$this->getAdapter()->isValidColumnType($action->getColumn())) {
305
            throw new InvalidArgumentException(sprintf(
306
                'An invalid column type "%s" was specified for column "%s".',
307
                $type,
308
                $action->getColumn()->getName()
309 196
            ));
310
        }
311 196
312 196
        $this->actions->addAction($action);
313 196
314 196
        return $this;
315 196
    }
316
317
    /**
318
     * Remove a table column.
319
     *
320
     * @param string $columnName Column Name
321
     *
322
     * @return $this
323
     */
324
    public function removeColumn($columnName)
325
    {
326
        $action = RemoveColumn::build($this->table, $columnName);
327
        $this->actions->addAction($action);
328
329
        return $this;
330
    }
331
332 210
    /**
333
     * Rename a table column.
334
     *
335 210
     * @param string $oldName Old Column Name
336 1
     * @param string $newName New Column Name
337
     *
338
     * @return $this
339
     */
340 209
    public function renameColumn($oldName, $newName)
341 207
    {
342 207
        $action = RenameColumn::build($this->table, $oldName, $newName);
343 207
        $this->actions->addAction($action);
344 207
345 207
        return $this;
346 2
    }
347
348
    /**
349
     * Change a table column type.
350 209
     *
351 1
     * @param string $columnName Column Name
352 1
     * @param string|\Phinx\Db\Table\Column|\Phinx\Util\Literal $newColumnType New Column Type
353 1
     * @param array $options Options
354 1
     *
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
     *
374
     * @return bool
375
     */
376
    public function hasColumn($columnName)
377
    {
378
        return $this->getAdapter()->hasColumn($this->getName(), $columnName);
379
    }
380
381 4
    /**
382
     * Add an index to a database table.
383 4
     *
384 4
     * In $options you can specific unique = true/false or name (index name).
385
     *
386
     * @param string|array|\Phinx\Db\Table\Index $columns Table Column(s)
387
     * @param array $options Index Options
388
     *
389
     * @return $this
390
     */
391
    public function addIndex($columns, array $options = [])
392
    {
393
        $action = AddIndex::build($this->table, $columns, $options);
394
        $this->actions->addAction($action);
395 17
396
        return $this;
397
    }
398 17
399 4
    /**
400 4
     * Removes the given index from a table.
401 4
     *
402 4
     * @param string|string[] $columns Columns
403 13
     *
404
     * @return $this
405
     */
406
    public function removeIndex($columns)
407 17
    {
408 15
        $action = DropIndex::build($this->table, is_string($columns) ? [$columns] : $columns);
409 15
        $this->actions->addAction($action);
410
411 17
        return $this;
412 17
    }
413
414
    /**
415
     * Removes the given index identified by its name from a table.
416
     *
417
     * @param string $name Index name
418
     *
419
     * @return $this
420
     */
421 89
    public function removeIndexByName($name)
422
    {
423 89
        $action = DropIndex::buildFromName($this->table, $name);
424
        $this->actions->addAction($action);
425
426
        return $this;
427
    }
428
429
    /**
430
     * Checks to see if an index exists.
431
     *
432
     * @param string|string[] $columns Columns
433
     *
434
     * @return bool
435 29
     */
436
    public function hasIndex($columns)
437
    {
438 29
        return $this->getAdapter()->hasIndex($this->getName(), $columns);
439 28
    }
440 28
441 22
    /**
442 22
     * Checks to see if an index specified by name exists.
443 28
     *
444 28
     * @param string $indexName Index name
445 28
     *
446 1
     * @return bool
447
     */
448
    public function hasIndexByName($indexName)
449 29
    {
450 29
        return $this->getAdapter()->hasIndexByName($this->getName(), $indexName);
451
    }
452
453
    /**
454
     * Add a foreign key to a database table.
455
     *
456
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
457
     * on_update, constraint = constraint name.
458
     *
459 1
     * @param string|string[] $columns Columns
460
     * @param string|\Phinx\Db\Table $referencedTable Referenced Table
461 1
     * @param string|string[] $referencedColumns Referenced Columns
462 1
     * @param array $options Options
463
     *
464
     * @return $this
465
     */
466
    public function addForeignKey($columns, $referencedTable, $referencedColumns = ['id'], $options = [])
467
    {
468
        $action = AddForeignKey::build($this->table, $columns, $referencedTable, $referencedColumns, $options);
0 ignored issues
show
Bug introduced by
It seems like $referencedTable can also be of type Phinx\Db\Table; however, parameter $referencedTable of Phinx\Db\Action\AddForeignKey::build() does only seem to accept Phinx\Db\Table\Table|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

468
        $action = AddForeignKey::build($this->table, $columns, /** @scrutinizer ignore-type */ $referencedTable, $referencedColumns, $options);
Loading history...
469
        $this->actions->addAction($action);
470
471 1
        return $this;
472
    }
473 1
474 1
    /**
475
     * Add a foreign key to a database table with a given name.
476
     *
477
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
478
     * on_update, constraint = constraint name.
479
     *
480
     * @param string $name The constraint name
481
     * @param string|string[] $columns Columns
482
     * @param string|\Phinx\Db\Table $referencedTable Referenced Table
483
     * @param string|string[] $referencedColumns Referenced Columns
484 12
     * @param array $options Options
485
     *
486 12
     * @return $this
487
     */
488
    public function addForeignKeyWithName($name, $columns, $referencedTable, $referencedColumns = ['id'], $options = [])
489
    {
490
        $action = AddForeignKey::build(
491
            $this->table,
492
            $columns,
493
            $referencedTable,
0 ignored issues
show
Bug introduced by
It seems like $referencedTable can also be of type Phinx\Db\Table; however, parameter $referencedTable of Phinx\Db\Action\AddForeignKey::build() does only seem to accept Phinx\Db\Table\Table|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

493
            /** @scrutinizer ignore-type */ $referencedTable,
Loading history...
494
            $referencedColumns,
495
            $options,
496
            $name
497
        );
498
        $this->actions->addAction($action);
499
500
        return $this;
501 8
    }
502
503 8
    /**
504 4
     * Removes the given foreign key from the table.
505 4
     *
506 8
     * @param string|string[] $columns Column(s)
507 8
     * @param string|null $constraint Constraint names
508
     *
509
     * @return $this
510 8
     */
511
    public function dropForeignKey($columns, $constraint = null)
512 8
    {
513 8
        $action = DropForeignKey::build($this->table, $columns, $constraint);
514 8
        $this->actions->addAction($action);
515 8
516
        return $this;
517 8
    }
518
519
    /**
520
     * Checks to see if a foreign key exists.
521
     *
522
     * @param string|string[] $columns Column(s)
523
     * @param string|null $constraint Constraint names
524
     *
525
     * @return bool
526
     */
527 1
    public function hasForeignKey($columns, $constraint = null)
528
    {
529 1
        return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint);
530 1
    }
531 1
532 1
    /**
533
     * Add timestamp columns created_at and updated_at to the table.
534
     *
535 1
     * @param string|null $createdAt Alternate name for the created_at column
536
     * @param string|null $updatedAt Alternate name for the updated_at column
537
     * @param bool $withTimezone Whether to set the timezone option on the added columns
538 1
     *
539
     * @return $this
540
     */
541
    public function addTimestamps($createdAt = 'created_at', $updatedAt = 'updated_at', $withTimezone = false)
542
    {
543
        $createdAt = $createdAt === null ? 'created_at' : $createdAt;
544
        $updatedAt = $updatedAt === null ? 'updated_at' : $updatedAt;
545
546
        $this->addColumn($createdAt, 'timestamp', [
547
                   'default' => 'CURRENT_TIMESTAMP',
548 1
                   'update' => '',
549
                   'timezone' => $withTimezone,
550 1
             ])
551
             ->addColumn($updatedAt, 'timestamp', [
552
                 'null' => true,
553
                 'default' => null,
554
                 'update' => 'CURRENT_TIMESTAMP',
555
                 'timezone' => $withTimezone,
556
             ]);
557
558
        return $this;
559
    }
560
561 15
    /**
562
     * Alias that always sets $withTimezone to true
563 15
     *
564 15
     * @see addTimestamps
565 15
     *
566 15
     * @param string|null $createdAt Alternate name for the created_at column
567
     * @param string|null $updatedAt Alternate name for the updated_at column
568 15
     *
569 15
     * @return $this
570 15
     */
571
    public function addTimestampsWithTimezone($createdAt = null, $updatedAt = null)
572 15
    {
573
        $this->addTimestamps($createdAt, $updatedAt, true);
574 15
575
        return $this;
576
    }
577
578
    /**
579
     * Insert data into the table.
580
     *
581
     * @param array $data array of data in the form:
582
     *              array(
583
     *                  array("col1" => "value1", "col2" => "anotherValue1"),
584
     *                  array("col2" => "value2", "col2" => "anotherValue2"),
585
     *              )
586
     *              or array("col1" => "value1", "col2" => "anotherValue1")
587
     *
588
     * @return $this
589 17
     */
590
    public function insert($data)
591
    {
592 17
        // handle array of array situations
593 11
        $keys = array_keys($data);
594 11
        $firstKey = array_shift($keys);
595 11
        if ($firstKey !== null && is_array($data[$firstKey])) {
596 11
            foreach ($data as $row) {
597
                $this->data[] = $row;
598 8
            }
599 8
600
            return $this;
601
        }
602
603
        if (count($data) > 0) {
604
            $this->data[] = $data;
605
        }
606
607 196
        return $this;
608
    }
609 196
610 196
    /**
611 196
     * Creates a table from the object instance.
612 196
     *
613
     * @return void
614
     */
615
    public function create()
616
    {
617
        $this->executeActions(false);
618
        $this->saveData();
619
        $this->reset(); // reset pending changes
620 46
    }
621
622 46
    /**
623
     * Updates a table from the object instance.
624
     *
625
     * @return void
626
     */
627 46
    public function update()
628 38
    {
629 46
        $this->executeActions(true);
630
        $this->saveData();
631 46
        $this->reset(); // reset pending changes
632 6
    }
633 46
634
    /**
635 46
     * Commit the pending data waiting for insertion.
636 3
     *
637 46
     * @return void
638
     */
639 46
    public function saveData()
640 46
    {
641 46
        $rows = $this->getData();
642
        if (empty($rows)) {
643
            return;
644
        }
645
646
        $bulk = true;
647
        $row = current($rows);
648 196
        $c = array_keys($row);
649
        foreach ($this->getData() as $row) {
650 196
            $k = array_keys($row);
651 196
            if ($k != $c) {
652 192
                $bulk = false;
653
                break;
654
            }
655 12
        }
656 12
657 12
        if ($bulk) {
658 12
            $this->getAdapter()->bulkinsert($this->table, $this->getData());
659 12
        } else {
660 12
            foreach ($this->getData() as $row) {
661 1
                $this->getAdapter()->insert($this->table, $row);
662 1
            }
663
        }
664 12
665
        $this->resetData();
666 12
    }
667 11
668 11
    /**
669 1
     * Immediately truncates the table. This operation cannot be undone
670 1
     *
671 1
     * @return void
672
     */
673 12
    public function truncate()
674
    {
675
        $this->getAdapter()->truncateTable($this->getName());
676
    }
677
678
    /**
679
     * Commits the table changes.
680 2
     *
681
     * If the table doesn't exist it is created otherwise it is updated.
682 2
     *
683 2
     * @return void
684
     */
685
    public function save()
686
    {
687
        if ($this->exists()) {
688
            $this->update(); // update the table
689
        } else {
690
            $this->create(); // create the table
691
        }
692 195
    }
693
694 195
    /**
695 45
     * Executes all the pending actions for this table
696 45
     *
697 195
     * @param bool $exists Whether or not the table existed prior to executing this method
698
     *
699
     * @return void
700 195
     */
701 195
    protected function executeActions($exists)
702
    {
703
        // Renaming a table is tricky, specially when running a reversible migration
704
        // down. We will just assume the table already exists if the user commands a
705
        // table rename.
706
        if (!$exists) {
707
            foreach ($this->actions->getActions() as $action) {
708
                if ($action instanceof RenameTable) {
709
                    $exists = true;
710
                    break;
711
                }
712
            }
713
        }
714
715
        // If the table does not exist, the last command in the chain needs to be
716
        // a CreateTable action.
717
        if (!$exists) {
718
            $this->actions->addAction(new CreateTable($this->table));
719
        }
720
721
        $plan = new Plan($this->actions);
722
        $plan->execute($this->getAdapter());
723
    }
724
}
725