Passed
Push — master ( 427eea...7afa64 )
by mark
05:54 queued 03:05
created

Table::hasPrimaryKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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