Completed
Pull Request — master (#1393)
by
unknown
03:00
created

SQLiteAdapter::hasTransactions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Cake\Database\Connection;
32
use Cake\Database\Driver\Sqlite as SqliteDriver;
33
use Phinx\Db\Table\Column;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Table\Index;
36
use Phinx\Db\Table\Table;
37
use Phinx\Db\Util\AlterInstructions;
38
use Phinx\Util\Literal;
39
40
/**
41
 * Phinx SQLite Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 * @author Richard McIntyre <[email protected]>
45
 */
46
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
47
{
48
    protected $definitionsWithLimits = [
49
        'CHARACTER',
50
        'VARCHAR',
51
        'VARYING CHARACTER',
52
        'NCHAR',
53
        'NATIVE CHARACTER',
54
        'NVARCHAR'
55
    ];
56 42
57
    protected $suffix = '.sqlite3';
58 42
59 42
    /**
60
     * {@inheritdoc}
61
     */
62
    public function connect()
63
    {
64
        if ($this->connection === null) {
65 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
66 42
                // @codeCoverageIgnoreStart
67
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
68
                // @codeCoverageIgnoreEnd
69 42
            }
70
71
            $db = null;
72 42
            $options = $this->getOptions();
73 42
74 42
            // if port is specified use it, otherwise use the MySQL default
75 42
            if (isset($options['memory'])) {
76
                $dsn = 'sqlite::memory:';
77
            } else {
78
                $dsn = 'sqlite:' . $options['name'] . $this->suffix;
79 42
            }
80 42
81
            try {
82
                $db = new \PDO($dsn);
83
            } catch (\PDOException $exception) {
84
                throw new \InvalidArgumentException(sprintf(
85
                    'There was a problem connecting to the database: %s',
86
                    $exception->getMessage()
87 42
                ));
88 42
            }
89 42
90
            $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
91
            $this->setConnection($db);
92
        }
93
    }
94 48
95
    /**
96 48
     * {@inheritdoc}
97 48
     */
98
    public function setOptions(array $options)
99
    {
100
        parent::setOptions($options);
101
102
        if (isset($options['suffix'])) {
103
            $this->suffix = $options['suffix'];
104
        }
105
        //don't "fix" the file extension if it is blank, some people
106
        //might want a SQLITE db file with absolutely no extension.
107
        if (strlen($this->suffix) && substr($this->suffix, 0, 1) !== '.') {
108
            $this->suffix = '.' . $this->suffix;
109
        }
110 1
111
        return $this;
112 1
    }
113 1
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function disconnect()
118
    {
119
        $this->connection = null;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function hasTransactions()
126
    {
127
        return true;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function beginTransaction()
134 43
    {
135
        $this->getConnection()->beginTransaction();
136 43
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function commitTransaction()
142 44
    {
143
        $this->getConnection()->commit();
144 44
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function rollbackTransaction()
150 42
    {
151
        $this->getConnection()->rollBack();
152 42
    }
153 42
154 42
    /**
155 12
     * {@inheritdoc}
156 42
     */
157
    public function quoteTableName($tableName)
158 42
    {
159
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
160
    }
161
162
    /**
163
     * {@inheritdoc}
164 42
     */
165
    public function quoteColumnName($columnName)
166
    {
167 42
        return '`' . str_replace('`', '``', $columnName) . '`';
168 42
    }
169 42
170 35
    /**
171 35
     * {@inheritdoc}
172 35
     */
173 35
    public function hasTable($tableName)
174
    {
175 35
        $tables = [];
176 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
177
        foreach ($rows as $row) {
178 1
            $tables[] = strtolower($row[0]);
179 1
        }
180 1
181 1
        return in_array(strtolower($tableName), $tables);
182
    }
183 1
184 1
    /**
185
     * {@inheritdoc}
186
     */
187 42
    public function createTable(Table $table, array $columns = [], array $indexes = [])
188 42
    {
189 42
        // Add the default primary key
190 42
        $options = $table->getOptions();
191 42
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
192
            $column = new Column();
193
            $column->setName('id')
194 42
                   ->setType('integer')
195 42
                   ->setIdentity(true);
196 42
197 42
            array_unshift($columns, $column);
198 42
        } elseif (isset($options['id']) && is_string($options['id'])) {
199 42
            // Handle id => "field_name" to support AUTO_INCREMENT
200
            $column = new Column();
201
            $column->setName($options['id'])
202 1
                   ->setType('integer')
203 1
                   ->setIdentity(true);
204 1
205
            array_unshift($columns, $column);
206 1
        }
207 1
208 1
        $sql = 'CREATE TABLE ';
209 1
        $sql .= $this->quoteTableName($table->getName()) . ' (';
210 1
        foreach ($columns as $column) {
211 1
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
212 42
213 42
            if (isset($options['primary_key']) && $column->getIdentity()) {
214 37
                //remove column from the primary key array as it is already defined as an autoincrement
215
                //primary id
216
                $identityColumnIndex = array_search($column->getName(), $options['primary_key']);
217
                if ($identityColumnIndex !== false) {
218 42
                    unset($options['primary_key'][$identityColumnIndex]);
219 42
220 1
                    if (empty($options['primary_key'])) {
221 1
                        //The last primary key has been removed
222 1
                        unset($options['primary_key']);
223 1
                    }
224
                }
225 42
            }
226
        }
227 42
228
        // set the primary key(s)
229 42 View Code Duplication
        if (isset($options['primary_key'])) {
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...
230 6
            $sql = rtrim($sql);
231 42
            $sql .= ' PRIMARY KEY (';
232 42
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
233
                $sql .= $this->quoteColumnName($options['primary_key']);
234
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
236
            }
237 1
            $sql .= ')';
238
        } else {
239 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
240 1
        }
241
242
        $sql = rtrim($sql) . ');';
243
        // execute the sql
244
        $this->execute($sql);
245 1
246
        foreach ($indexes as $index) {
247 1
            $this->addIndex($table, $index);
248 1
        }
249
    }
250
251
    /**
252
     * {@inheritdoc}
253 1
     */
254
    protected function getChangeTableInstructions(Table $table, array $newOptions)
255 1
    {
256 1
        $instructions = new AlterInstructions();
257 1
258 1
        // Drop the existing primary key
259
        $primaryKey = $this->getPrimaryKey($table->getName());
260 1
        if ((isset($newOptions['id']) || isset($newOptions['primary_key']))
261 1
            && !empty($primaryKey))
262
        {
263
            $this->dropPrimaryKey($table->getName(), $primaryKey);
264
        }
265
266 1
        // Set the default primary key and add associated column
267 View Code Duplication
        if (isset($newOptions['id']) && $newOptions['id'] !== false) {
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...
268 1
            if ($newOptions['id'] === true) {
269 1
                $newOptions['primary_key'] = 'id';
270
            } else if (is_string($newOptions['id'])) {
271 1
                // Handle id => "field_name" to support AUTO_INCREMENT
272 1
                $newOptions['primary_key'] = $newOptions['id'];
273 1
            } else {
274 1
                throw new \InvalidArgumentException(sprintf(
275 1
                    "Invalid value for option 'id': %s",
276 1
                    json_encode($newOptions['id'])
277
                ));
278 1
            }
279 1
280 1
            if ($this->hasColumn($table->getName(), $newOptions['primary_key'])) {
281
                throw new \RuntimeException(sprintf(
282 1
                    "Tried to create primary key column %s for table %s, but that column already exists",
283 1
                    $this->quoteColumnName($newOptions['primary_key']),
284 1
                    $this->quoteTableName($table->getName())
285
                ));
286 1
            }
287 1
288
            $column = new Column();
289 1
            $column
290
                ->setName($newOptions['primary_key'])
291
                ->setType('integer');
292
            $instructions->merge($this->getAddColumnInstructions($table, $column));
293
        }
294
295 8
        // Add the primary key(s)
296
        if (isset($newOptions['primary_key']) && $newOptions['primary_key'] !== false) {
297 8
            if (!is_string($newOptions['primary_key'])) {
298 8
                throw new \InvalidArgumentException(sprintf(
299 8
                    "Invalid value for option 'primary_key': %s",
300 7
                    json_encode($newOptions['primary_key'])
301
                ));
302 8
            }
303
304 8
            $instructions->merge(
305
                $this->getAddPrimaryKeyInstructions($table, $newOptions['primary_key'])
306
            );
307
        }
308
309
        return $instructions;
310 4
    }
311
312 4
    /**
313 4
     * {@inheritdoc}
314 4
     */
315 4 View Code Duplication
    protected function getRenameTableInstructions($tableName, $newTableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
316 4
    {
317 4
        $sql = sprintf(
318
            'ALTER TABLE %s RENAME TO %s',
319 4
            $this->quoteTableName($tableName),
320 4
            $this->quoteTableName($newTableName)
321
        );
322
323
        return new AlterInstructions([], [$sql]);
324
    }
325 2
326
    /**
327 2
     * {@inheritdoc}
328
     */
329 2
    protected function getDropTableInstructions($tableName)
330
    {
331 2
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
332 2
333 2
        return new AlterInstructions([], [$sql]);
334 2
    }
335 2
336 2
    /**
337
     * {@inheritdoc}
338 2
     */
339 2
    public function truncateTable($tableName)
340 2
    {
341 2
        $sql = sprintf(
342 2
            'DELETE FROM %s',
343 2
            $this->quoteTableName($tableName)
344 2
        );
345 2
346 2
        $this->execute($sql);
347
    }
348 2
349 1
    /**
350
     * {@inheritdoc}
351 1
     */
352
    public function getColumns($tableName)
353
    {
354 1
        $columns = [];
355
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
356 1
357 1
        foreach ($rows as $columnInfo) {
358 1
            $column = new Column();
359
            $type = strtolower($columnInfo['type']);
360 1
            $column->setName($columnInfo['name'])
361 1
                   ->setNull($columnInfo['notnull'] !== '1')
362
                   ->setDefault($columnInfo['dflt_value']);
363
364 1
            $phinxType = $this->getPhinxType($type);
365 1
            $column->setType($phinxType['name'])
366 1
                   ->setLimit($phinxType['limit']);
367 1
368 1
            if ($columnInfo['pk'] == 1) {
369
                $column->setIdentity(true);
370 1
            }
371
372 1
            $columns[] = $column;
373
        }
374 1
375 1
        return $columns;
376
    }
377
378
    /**
379
     * {@inheritdoc}
380 6
     */
381 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
382
    {
383
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
384 6
        foreach ($rows as $column) {
385
            if (strcasecmp($column['name'], $columnName) === 0) {
386 6
                return true;
387
            }
388 6
        }
389 6
390 6
        return false;
391 6
    }
392 6
393 6
    /**
394
     * {@inheritdoc}
395 6
     */
396 6 View Code Duplication
    protected function getAddColumnInstructions(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
397 6
    {
398 6
        $alter = sprintf(
399 6
            'ADD COLUMN %s %s',
400 6
            $this->quoteColumnName($column->getName()),
401 6
            $this->getColumnSqlDefinition($column)
402 6
        );
403 6
404
        return new AlterInstructions([$alter]);
405 6
    }
406
407
    /**
408
     * Returns the original CREATE statement for the give table
409
     *
410
     * @param string $tableName The table name to get the create statement for
411 6
     * @return string
412
     */
413 6
    protected function getDeclaringSql($tableName)
414 6
    {
415 6
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
416 6
417
        $sql = '';
418 6
        foreach ($rows as $table) {
419
            if ($table['tbl_name'] === $tableName) {
420 6
                $sql = $table['sql'];
421
            }
422 6
        }
423 6
424 6
        return $sql;
425 6
    }
426 6
427
    /**
428 6
     * Copies all the data from a tmp table to another table
429
     *
430 6
     * @param string $tableName The table name to copy the data to
431 6
     * @param string $tmpTableName The tmp table name where the data is stored
432 6
     * @param string[] $writeColumns The list of columns in the target table
433
     * @param string[] $selectColumns The list of columns in the tmp table
434
     * @return void
435
     */
436
    protected function copyDataToNewTable($tableName, $tmpTableName, $writeColumns, $selectColumns)
437 2
    {
438
        $sql = sprintf(
439
            'INSERT INTO %s(%s) SELECT %s FROM %s',
440 2
            $this->quoteTableName($tableName),
441
            implode(', ', $writeColumns),
442 2
            implode(', ', $selectColumns),
443
            $this->quoteTableName($tmpTableName)
444 2
        );
445 2
        $this->execute($sql);
446 2
    }
447 2
448 2
    /**
449 2
     * Modifies the passed instructions to copy all data from the tmp table into
450
     * the provided table and then drops the tmp table.
451 2
     *
452 2
     * @param AlterInstructions $instructions The instructions to modify
453 2
     * @param string $tableName The table name to copy the data to
454 2
     * @return AlterInstructions
455 2
     */
456 2
    protected function copyAndDropTmpTable($instructions, $tableName)
457 2
    {
458 2
        $instructions->addPostStep(function ($state) use ($tableName) {
459 2
            $this->copyDataToNewTable(
460
                $tableName,
461 2
                $state['tmpTableName'],
462
                $state['writeColumns'],
463 2
                $state['selectColumns']
464
            );
465
466
            $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($state['tmpTableName'])));
467
468
            return $state;
469 2
        });
470
471 2
        return $instructions;
472 2
    }
473 2
474
    /**
475 2
     * Returns the columns and type to use when copying a table to another in the process
476
     * of altering a table
477 2
     *
478 2
     * @param string $tableName The table to modify
479 2
     * @param string $columnName The column name that is about to change
480
     * @param string|false $newColumnName Optionally the new name for the column
481 2
     * @return AlterInstructions
482
     */
483 2
    protected function calculateNewTableColumns($tableName, $columnName, $newColumnName)
484 2
    {
485 2
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
486 2
        $selectColumns = [];
487 2
        $writeColumns = [];
488
        $columnType = null;
489 2
        $found = false;
490
491 2
        foreach ($columns as $column) {
492 2
            $selectName = $column['name'];
493 2
            $writeName = $selectName;
494
495
            if ($selectName == $columnName) {
496
                $writeName = $newColumnName;
497
                $found = true;
498
                $columnType = $column['type'];
499
                $selectName = $newColumnName === false ? $newColumnName : $selectName;
500
            }
501 9
502
            $selectColumns[] = $selectName;
503 9
            $writeColumns[] = $writeName;
504 9
        }
505
506 9
        $selectColumns = array_filter($selectColumns, 'strlen');
507 9
        $writeColumns = array_filter($writeColumns, 'strlen');
508 9
        $selectColumns = array_map([$this, 'quoteColumnName'], $selectColumns);
509 9
        $writeColumns = array_map([$this, 'quoteColumnName'], $writeColumns);
510 9
511 9
        if (!$found) {
512 9
            throw new \InvalidArgumentException(sprintf(
513 9
                'The specified column doesn\'t exist: ' . $columnName
514 9
            ));
515 9
        }
516
517
        return compact('writeColumns', 'selectColumns', 'columnType');
518
    }
519
520
    /**
521 9
     * Returns the initial instructions to alter a table using the
522
     * rename-alter-copy strategy
523 9
     *
524 4
     * @param string $tableName The table to modify
525 4
     * @return AlterInstructions
526
     */
527 9
    protected function beginAlterByCopyTable($tableName)
528 9
    {
529
        $instructions = new AlterInstructions();
530 9
        $instructions->addPostStep(function ($state) use ($tableName) {
531 9
            $createSQL = $this->getDeclaringSql($tableName);
532 9
533 9
            $tmpTableName = 'tmp_' . $tableName;
534
            $this->execute(
535 8
                sprintf(
536
                    'ALTER TABLE %s RENAME TO %s',
537 8
                    $this->quoteTableName($tableName),
538
                    $this->quoteTableName($tmpTableName)
539
                )
540
            );
541
542
            return compact('createSQL', 'tmpTableName') + $state;
543 1
        });
544
545 1
        return $instructions;
546
    }
547 1
548 1
    /**
549 1
     * {@inheritdoc}
550
     */
551
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
552
    {
553
        $instructions = $this->beginAlterByCopyTable($tableName);
554 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
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...
555
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
556
557
            return $newState + $state;
558
        });
559 8
560
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
561 8
            $sql = str_replace(
562 8
                $this->quoteColumnName($columnName),
563 8
                $this->quoteColumnName($newColumnName),
564 8
                $state['createSQL']
565 8
            );
566 8
            $this->execute($sql);
567 8
568 8
            return $state;
569 8
        });
570 8
571
        return $this->copyAndDropTmpTable($instructions, $tableName);
572 8
    }
573 8
574 8
    /**
575
     * {@inheritdoc}
576
     */
577
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
578
    {
579 1
        $instructions = $this->beginAlterByCopyTable($tableName);
580
581 1
        $newColumnName = $newColumn->getName();
582 1 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName, $newColumnName) {
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...
583 1
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, $newColumnName);
584
585 1
            return $newState + $state;
586 1
        });
587
588 1
        $instructions->addPostStep(function ($state) use ($columnName, $newColumn) {
589 1
            $sql = preg_replace(
590 1
                sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
591 1
                sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
592 1
                $state['createSQL'],
593 1
                1
594 1
            );
595 1
            $this->execute($sql);
596 1
597 1
            return $state;
598
        });
599
600
        return $this->copyAndDropTmpTable($instructions, $tableName);
601
    }
602
603
    /**
604
     * {@inheritdoc}
605 1
     */
606
    protected function getDropColumnInstructions($tableName, $columnName)
607 1
    {
608
        $instructions = $this->beginAlterByCopyTable($tableName);
609 1
610 1 View Code Duplication
        $instructions->addPostStep(function ($state) use ($columnName) {
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...
611 1
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columnName, false);
612 1
613 1
            return $newState + $state;
614 1
        });
615 1
616 1
        $instructions->addPostStep(function ($state) use ($columnName) {
617 1
            $sql = preg_replace(
618
                sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])),
619
                "",
620
                $state['createSQL']
621
            );
622
623
            if (substr($sql, -2) === ', ') {
624
                $sql = substr($sql, 0, -2) . ')';
625 5
            }
626
627 5
            $this->execute($sql);
628
629
            return $state;
630 5
        });
631
632 5
        return $this->copyAndDropTmpTable($instructions, $tableName);
633 5
    }
634 5
635
    /**
636 1
     * Get an array of indexes from a particular table.
637
     *
638
     * @param string $tableName Table Name
639
     * @return array
640
     */
641
    protected function getIndexes($tableName)
642
    {
643
        $indexes = [];
644
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
645 5
646
        foreach ($rows as $row) {
647 5
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
648 5
            if (!isset($indexes[$tableName])) {
649
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
650
            }
651
            foreach ($indexData as $indexItem) {
652
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
653
            }
654
        }
655
656
        return $indexes;
657
    }
658
659
    /**
660
     * {@inheritdoc}
661 5
     */
662 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
663 5
    {
664 5
        if (is_string($columns)) {
665 5
            $columns = [$columns]; // str to array
666 5
        }
667 5
668 5
        $columns = array_map('strtolower', $columns);
669 5
        $indexes = $this->getIndexes($tableName);
670 5
671 5
        foreach ($indexes as $index) {
672 5
            $a = array_diff($columns, $index['columns']);
673 5
            if (empty($a)) {
674
                return true;
675
            }
676
        }
677
678
        return false;
679 4
    }
680
681
    /**
682 4
     * {@inheritdoc}
683
     */
684 4 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
685 4
    {
686
        $indexes = $this->getIndexes($tableName);
687 4
688 4
        foreach ($indexes as $index) {
689 4
            if ($indexName === $index['index']) {
690 4
                return true;
691 4
            }
692 4
        }
693
694 4
        return false;
695 4
    }
696 4
697 4
    /**
698 4
     * {@inheritdoc}
699
     */
700 4
    protected function getAddIndexInstructions(Table $table, Index $index)
701
    {
702 4
        $indexColumnArray = [];
703 4
        foreach ($index->getColumns() as $column) {
704
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
705 4
        }
706 4
        $indexColumns = implode(',', $indexColumnArray);
707 4
        $sql = sprintf(
708 4
            'CREATE %s ON %s (%s)',
709 4
            $this->getIndexSqlDefinition($table, $index),
710 4
            $this->quoteTableName($table->getName()),
711 4
            $indexColumns
712
        );
713 4
714 4
        return new AlterInstructions([], [$sql]);
715 4
    }
716
717
    /**
718
     * {@inheritdoc}
719
     */
720 1
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
721
    {
722
        if (is_string($columns)) {
723 1
            $columns = [$columns]; // str to array
724
        }
725
726
        $indexes = $this->getIndexes($tableName);
727 1
        $columns = array_map('strtolower', $columns);
728
        $instructions = new AlterInstructions();
729 1
730
        foreach ($indexes as $index) {
731 1
            $a = array_diff($columns, $index['columns']);
732 1
            if (empty($a)) {
733 1
                $instructions->addPostStep(sprintf(
734 1
                    'DROP INDEX %s',
735 1
                    $this->quoteColumnName($index['index'])
736 1
                ));
737
            }
738 1
        }
739 1
740 1
        return $instructions;
741 1
    }
742 1
743 1
    /**
744 1
     * {@inheritdoc}
745
     */
746 1
    protected function getDropIndexByNameInstructions($tableName, $indexName)
747
    {
748 1
        $indexes = $this->getIndexes($tableName);
749
        $instructions = new AlterInstructions();
750
751
        foreach ($indexes as $index) {
752
            if ($indexName === $index['index']) {
753
                $instructions->addPostStep(sprintf(
754 1
                    'DROP INDEX %s',
755
                    $this->quoteColumnName($indexName)
756 1
                ));
757 1
            }
758 1
        }
759 1
760 1
        return $instructions;
761 1
    }
762 1
763
    /**
764 1
     * {@inheritdoc}
765
     */
766 1
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
767 1
    {
768 1
        $primaryKey = $this->getPrimaryKey($tableName);
769 1
770 1
        if (empty($primaryKey)) {
771
            return false;
772 1
        }
773
774 1
        if (is_string($columns)) {
775 1
            $columns = [$columns]; // str to array
776 1
        }
777
        $missingColumns = array_diff($columns, [$primaryKey]);
778
        return empty($missingColumns);
779
    }
780
781
    /**
782
     * Get the primary key from a particular table.
783
     *
784
     * @param string $tableName Table Name
785
     * @return string|null
786
     */
787
    protected function getPrimaryKey($tableName)
788
    {
789
        $rows = $this->fetchAll(
790
            "SELECT sql, tbl_name
791
              FROM (
792
                    SELECT sql sql, type type, tbl_name tbl_name, name name
793
                      FROM sqlite_master
794
                     UNION ALL
795
                    SELECT sql, type, tbl_name, name
796
                      FROM sqlite_temp_master
797
                   )
798
             WHERE type != 'meta'
799
               AND sql NOTNULL
800
               AND name NOT LIKE 'sqlite_%'
801
             ORDER BY substr(type, 2, 1), name"
802
        );
803
804
        foreach ($rows as $row) {
805
            if ($row['tbl_name'] === $tableName) {
806
                if (strpos($row['sql'], 'PRIMARY KEY') !== false) {
807
                    preg_match_all("/PRIMARY KEY\s*\(`([^`]*)`\)/", $row['sql'], $matches);
808
                    foreach ($matches[1] as $match) {
809
                        if (!empty($match)) return $match;
810 43
                    }
811
                    preg_match_all("/`([^`]+)`[\w\s]+PRIMARY KEY/", $row['sql'], $matches);
812
                    foreach ($matches[1] as $match) {
813 43
                        if (!empty($match)) return $match;
814 42
                    }
815
                }
816 43
            }
817
        }
818
819 43
        return null;
820 1
    }
821
822 43
    /**
823 38
     * {@inheritdoc}
824
     */
825 43
    public function hasForeignKey($tableName, $columns, $constraint = null)
826 42
    {
827
        if (is_string($columns)) {
828 43
            $columns = [$columns]; // str to array
829 2
        }
830
        $foreignKeys = $this->getForeignKeys($tableName);
831 43
832 1
        return !array_diff($columns, $foreignKeys);
833
    }
834 43
835 1
    /**
836
     * Get an array of foreign keys from a particular table.
837 43
     *
838 42
     * @param string $tableName Table Name
839
     * @return array
840 43
     */
841 1
    protected function getForeignKeys($tableName)
842
    {
843 43
        $foreignKeys = [];
844 1
        $rows = $this->fetchAll(
845
            "SELECT sql, tbl_name
846 43
              FROM (
847 43
                    SELECT sql sql, type type, tbl_name tbl_name, name name
848 1
                      FROM sqlite_master
849
                     UNION ALL
850 43
                    SELECT sql, type, tbl_name, name
851 42
                      FROM sqlite_temp_master
852
                   )
853 5
             WHERE type != 'meta'
854
               AND sql NOTNULL
855 5
               AND name NOT LIKE 'sqlite_%'
856 4
             ORDER BY substr(type, 2, 1), name"
857
        );
858
859
        foreach ($rows as $row) {
860 1
            if ($row['tbl_name'] === $tableName) {
861 1
                if (strpos($row['sql'], 'REFERENCES') !== false) {
862
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
863
                    foreach ($matches[1] as $match) {
864 1
                        $foreignKeys[] = $match;
865
                    }
866
                }
867 1
            }
868
        }
869 1
870 1
        return $foreignKeys;
871 1
    }
872
873
    /**
874
     * @param Table $table
875
     * @param string $column
876
     * @return AlterInstructions
877
     */
878
    protected function getAddPrimaryKeyInstructions(Table $table, string $column)
879
    {
880 3
        $instructions = $this->beginAlterByCopyTable($table->getName());
881
882 3
        $tableName = $table->getName();
883 1 View Code Duplication
        $instructions->addPostStep(function ($state) use ($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...
884
            $sql = preg_replace("/(`$column`)\s+\w+\s+((NOT )?NULL)/", '$1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT', $state['createSQL'], 1);
885 2
            $this->execute($sql);
886 2
887 2
            return $state;
888 2
        });
889 1
890 1 View Code Duplication
        $instructions->addPostStep(function ($state) {
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...
891 2
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
892
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
893
            $selectColumns = $writeColumns = $names;
894 2
895 2
            return compact('selectColumns', 'writeColumns') + $state;
896 1
        });
897 1
898
        return $this->copyAndDropTmpTable($instructions, $tableName);
899
    }
900 1
901 2
    /**
902
     * @param string $tableName
903
     * @param string $column
904
     */
905
    protected function dropPrimaryKey(string $tableName, string $column)
906
    {
907
        $instructions = $this->beginAlterByCopyTable($tableName);
908
909
        $instructions->addPostStep(function ($state) use ($column) {
910 2
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $column, $column);
911
912
            return $newState + $state;
913
        });
914
915 View Code Duplication
        $instructions->addPostStep(function ($state) {
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...
916 2
            $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/";
917 1
            $sql = preg_replace($search, '', $state['createSQL'], 1);
918
919
            if ($sql) {
920 1
                $this->execute($sql);
921 1
            }
922 2
923 1
            return $state;
924 1
        });
925 2
926 2
        $instructions = $this->copyAndDropTmpTable($instructions, $tableName);
927
928
        $instructions->execute(null, [$this, 'execute']);
929
    }
930
931
    /**
932
     * {@inheritdoc}
933 2
     */
934
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
935
    {
936 1
        $instructions = $this->beginAlterByCopyTable($table->getName());
937 1
938
        $tableName = $table->getName();
939 1
        $instructions->addPostStep(function ($state) use ($foreignKey) {
940
            $this->execute('pragma foreign_keys = ON');
941
            $sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
942
            $this->execute($sql);
943
944
            return $state;
945
        });
946 48
947 View Code Duplication
        $instructions->addPostStep(function ($state) {
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...
948 48
            $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($state['tmpTableName'])));
949 48
            $names = array_map([$this, 'quoteColumnName'], array_column($columns, 'name'));
950
            $selectColumns = $writeColumns = $names;
951
952
            return compact('selectColumns', 'writeColumns') + $state;
953
        });
954 2
955
        return $this->copyAndDropTmpTable($instructions, $tableName);
956 2
    }
957
958
    /**
959
     * {@inheritdoc}
960
     */
961
    protected function getDropForeignKeyInstructions($tableName, $constraint)
962 48
    {
963
        throw new \BadMethodCallException('SQLite does not have named foreign keys');
964 48
    }
965 47
966 47
    /**
967 48
     * {@inheritdoc}
968
     */
969
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
970
    {
971
        $instructions = $this->beginAlterByCopyTable($tableName);
972
973
        $instructions->addPostStep(function ($state) use ($columns) {
974
            $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]);
975 42
976
            $selectColumns = $newState['selectColumns'];
977 42
            $columns = array_map([$this, 'quoteColumnName'], $columns);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $columns, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
978 8
            $diff = array_diff($columns, $selectColumns);
979 42
980 42
            if (!empty($diff)) {
981 42
                throw new \InvalidArgumentException(sprintf(
982 42
                    'The specified columns doen\'t exist: ' . implode(', ', $diff)
983
                ));
984
            }
985
986
            return $newState + $state;
987
        });
988
989
        $instructions->addPostStep(function ($state) use ($columns) {
990
            $sql = '';
991 42
992
            foreach ($columns as $columnName) {
993 42
                $search = sprintf(
994 42
                    "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
995 42
                    $this->quoteColumnName($columnName)
996 42
                );
997
                $sql = preg_replace($search, '', $state['createSQL'], 1);
998
            }
999 42
1000 42
            if ($sql) {
1001 42
                $this->execute($sql);
1002 42
            }
1003 42
1004 4
            return $state;
1005 4
        });
1006
1007 42
        return $this->copyAndDropTmpTable($instructions, $tableName);
1008
    }
1009 42
1010 42
    /**
1011 42
     * {@inheritdoc}
1012
     */
1013 42
    public function getSqlType($type, $limit = null)
1014
    {
1015
        switch ($type) {
1016
            case static::PHINX_TYPE_TEXT:
1017 42
            case static::PHINX_TYPE_INTEGER:
1018
            case static::PHINX_TYPE_FLOAT:
1019 42
            case static::PHINX_TYPE_DECIMAL:
1020
            case static::PHINX_TYPE_DATETIME:
1021
            case static::PHINX_TYPE_TIME:
1022
            case static::PHINX_TYPE_DATE:
1023
            case static::PHINX_TYPE_BLOB:
1024
            case static::PHINX_TYPE_BOOLEAN:
1025
            case static::PHINX_TYPE_ENUM:
1026
                return ['name' => $type];
1027
            case static::PHINX_TYPE_STRING:
1028 42
                return ['name' => 'varchar', 'limit' => 255];
1029
            case static::PHINX_TYPE_CHAR:
1030 42
                return ['name' => 'char', 'limit' => 255];
1031 2
            case static::PHINX_TYPE_BIG_INTEGER:
1032
                return ['name' => 'bigint'];
1033 42
            case static::PHINX_TYPE_TIMESTAMP:
1034
                return ['name' => 'datetime'];
1035
            case static::PHINX_TYPE_BINARY:
1036
                return ['name' => 'blob'];
1037
            case static::PHINX_TYPE_UUID:
1038
                return ['name' => 'char', 'limit' => 36];
1039
            case static::PHINX_TYPE_JSON:
1040
            case static::PHINX_TYPE_JSONB:
1041
                return ['name' => 'text'];
1042 8
            // Geospatial database types
1043
            // No specific data types exist in SQLite, instead all geospatial
1044 8
            // functionality is handled in the client. See also: SpatiaLite.
1045 2
            case static::PHINX_TYPE_GEOMETRY:
1046 2
            case static::PHINX_TYPE_POLYGON:
1047 6
                return ['name' => 'text'];
1048
            case static::PHINX_TYPE_LINESTRING:
1049 8
                return ['name' => 'varchar', 'limit' => 255];
1050 3
            case static::PHINX_TYPE_POINT:
1051 3
                return ['name' => 'float'];
1052 6
            default:
1053 6
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
1054 6
        }
1055 6
    }
1056 6
1057
    /**
1058 8
     * Returns Phinx type by SQL type
1059 8
     *
1060
     * @param string $sqlTypeDef SQL type
1061
     * @returns string Phinx type
1062
     */
1063
    public function getPhinxType($sqlTypeDef)
1064
    {
1065 47
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
1066
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
1067 47
        } else {
1068
            $limit = null;
1069
            $precision = null;
1070
            $type = $matches[1];
1071
            if (count($matches) > 2) {
1072
                $limit = $matches[3] ?: null;
1073
            }
1074
            if (count($matches) > 4) {
1075
                $precision = $matches[5];
1076 5
            }
1077
            switch ($matches[1]) {
1078 5
                case 'varchar':
1079 5
                    $type = static::PHINX_TYPE_STRING;
1080
                    if ($limit === 255) {
1081
                        $limit = null;
1082 5
                    }
1083 5
                    break;
1084 5 View Code Duplication
                case 'char':
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...
1085 5
                    $type = static::PHINX_TYPE_CHAR;
1086 5
                    if ($limit === 255) {
1087 5
                        $limit = null;
1088 5
                    }
1089 5
                    if ($limit === 36) {
1090 5
                        $type = static::PHINX_TYPE_UUID;
1091 5
                    }
1092 5
                    break;
1093 1
                case 'int':
1094 1
                    $type = static::PHINX_TYPE_INTEGER;
1095 5
                    if ($limit === 11) {
1096 1
                        $limit = null;
1097 1
                    }
1098
                    break;
1099 5
                case 'bigint':
1100
                    if ($limit === 11) {
1101
                        $limit = null;
1102
                    }
1103
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1104
                    break;
1105
                case 'blob':
1106
                    $type = static::PHINX_TYPE_BINARY;
1107
                    break;
1108
            }
1109 View Code Duplication
            if ($type === 'tinyint') {
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...
1110
                if ($matches[3] === 1) {
1111
                    $type = static::PHINX_TYPE_BOOLEAN;
1112
                    $limit = null;
1113
                }
1114
            }
1115
1116
            $this->getSqlType($type);
1117
1118
            return [
1119
                'name' => $type,
1120
                'limit' => $limit,
1121
                'precision' => $precision
1122
            ];
1123
        }
1124
    }
1125
1126
    /**
1127
     * {@inheritdoc}
1128
     */
1129
    public function createDatabase($name, $options = [])
1130
    {
1131
        touch($name . $this->suffix);
1132
    }
1133
1134
    /**
1135
     * {@inheritdoc}
1136
     */
1137
    public function hasDatabase($name)
1138
    {
1139
        return is_file($name . $this->suffix);
1140
    }
1141
1142
    /**
1143
     * {@inheritdoc}
1144
     */
1145
    public function dropDatabase($name)
1146
    {
1147
        if (file_exists($name . '.sqlite3')) {
1148
            unlink($name . '.sqlite3');
1149
        }
1150
    }
1151
1152
    /**
1153
     * Gets the SQLite Column Definition for a Column object.
1154
     *
1155
     * @param \Phinx\Db\Table\Column $column Column
1156
     * @return string
1157
     */
1158
    protected function getColumnSqlDefinition(Column $column)
1159
    {
1160
        $isLiteralType = $column->getType() instanceof Literal;
1161
        if ($isLiteralType) {
1162
            $def = (string)$column->getType();
1163
        } else {
1164
            $sqlType = $this->getSqlType($column->getType());
1165
            $def = strtoupper($sqlType['name']);
1166
1167
            $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
1168
            if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
1169
                $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
1170
            }
1171
        }
1172 View Code Duplication
        if ($column->getPrecision() && $column->getScale()) {
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...
1173
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1174
        }
1175 View Code Duplication
        if (($values = $column->getValues()) && is_array($values)) {
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...
1176
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
1177
        }
1178
1179
        $default = $column->getDefault();
1180
1181
        $def .= (!$column->isIdentity() && ($column->isNull() || is_null($default))) ? ' NULL' : ' NOT NULL';
1182
        $def .= $this->getDefaultValueDefinition($default, $column->getType());
0 ignored issues
show
Bug introduced by
It seems like $column->getType() targeting Phinx\Db\Table\Column::getType() can also be of type object<Phinx\Util\Literal>; however, Phinx\Db\Adapter\PdoAdap...efaultValueDefinition() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1183
        $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
1184
1185
        if ($column->getUpdate()) {
1186
            $def .= ' ON UPDATE ' . $column->getUpdate();
1187
        }
1188
1189
        $def .= $this->getCommentDefinition($column);
1190
1191
        return $def;
1192
    }
1193
1194
    /**
1195
     * Gets the comment Definition for a Column object.
1196
     *
1197
     * @param \Phinx\Db\Table\Column $column Column
1198
     * @return string
1199
     */
1200
    protected function getCommentDefinition(Column $column)
1201
    {
1202
        if ($column->getComment()) {
1203
            return ' /* ' . $column->getComment() . ' */ ';
1204
        }
1205
1206
        return '';
1207
    }
1208
1209
    /**
1210
     * Gets the SQLite Index Definition for an Index object.
1211
     *
1212
     * @param \Phinx\Db\Table\Table $table Table
1213
     * @param \Phinx\Db\Table\Index $index Index
1214
     * @return string
1215
     */
1216
    protected function getIndexSqlDefinition(Table $table, Index $index)
1217
    {
1218
        if ($index->getType() === Index::UNIQUE) {
1219
            $def = 'UNIQUE INDEX';
1220
        } else {
1221
            $def = 'INDEX';
1222
        }
1223
        if (is_string($index->getName())) {
1224
            $indexName = $index->getName();
1225
        } else {
1226
            $indexName = $table->getName() . '_';
1227
            foreach ($index->getColumns() as $column) {
1228
                $indexName .= $column . '_';
1229
            }
1230
            $indexName .= 'index';
1231
        }
1232
        $def .= ' `' . $indexName . '`';
1233
1234
        return $def;
1235
    }
1236
1237
    /**
1238
     * {@inheritdoc}
1239
     */
1240
    public function getColumnTypes()
1241
    {
1242
        return array_merge(parent::getColumnTypes(), ['enum', 'json', 'jsonb']);
1243
    }
1244
1245
    /**
1246
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1247
     *
1248
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1249
     * @return string
1250
     */
1251 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
1252
    {
1253
        $def = '';
1254
        if ($foreignKey->getConstraint()) {
1255
            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
0 ignored issues
show
Bug introduced by
It seems like $foreignKey->getConstraint() targeting Phinx\Db\Table\ForeignKey::getConstraint() can also be of type boolean; however, Phinx\Db\Adapter\SQLiteAdapter::quoteColumnName() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1256
        } else {
1257
            $columnNames = [];
1258
            foreach ($foreignKey->getColumns() as $column) {
1259
                $columnNames[] = $this->quoteColumnName($column);
1260
            }
1261
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1262
            $refColumnNames = [];
1263
            foreach ($foreignKey->getReferencedColumns() as $column) {
1264
                $refColumnNames[] = $this->quoteColumnName($column);
1265
            }
1266
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1267
            if ($foreignKey->getOnDelete()) {
1268
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1269
            }
1270
            if ($foreignKey->getOnUpdate()) {
1271
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1272
            }
1273
        }
1274
1275
        return $def;
1276
    }
1277
1278
    /**
1279
     * {@inheritDoc}
1280
     *
1281
     */
1282
    public function getDecoratedConnection()
1283
    {
1284
        $options = $this->getOptions();
1285
        $options['quoteIdentifiers'] = true;
1286
        $database = ':memory:';
0 ignored issues
show
Unused Code introduced by
$database is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1287
1288
        if (!empty($options['name'])) {
1289
            $options['database'] = $options['name'];
1290
1291
            if (file_exists($options['name'] . $this->suffix)) {
1292
                $options['database'] = $options['name'] . $this->suffix;
1293
            }
1294
        }
1295
1296
        $driver = new SqliteDriver($options);
1297
        if (method_exists($driver, 'setConnection')) {
1298
            $driver->setConnection($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can be null; however, setConnection() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1299
        } else {
1300
            $driver->connection($this->connection);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Database\Driver::connection() has been deprecated with message: 3.6.0 Use getConnection()/setConnection() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1301
        }
1302
1303
        return new Connection(['driver' => $driver] + $options);
1304
    }
1305
}
1306