Completed
Pull Request — master (#1175)
by David Joseph
01:44
created

SQLiteAdapter::hasForeignKey()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.4746

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 5
cts 8
cp 0.625
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 4
nop 3
crap 3.4746
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 Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\ForeignKey;
34
use Phinx\Db\Table\Index;
35
36
/**
37
 * Phinx SQLite Adapter.
38
 *
39
 * @author Rob Morgan <[email protected]>
40
 * @author Richard McIntyre <[email protected]>
41
 */
42
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
43
{
44
    protected $definitionsWithLimits = [
45
        'CHARACTER',
46
        'VARCHAR',
47
        'VARYING CHARACTER',
48
        'NCHAR',
49
        'NATIVE CHARACTER',
50
        'NVARCHAR'
51
    ];
52
53
    /**
54
     * {@inheritdoc}
55
     */
56 42
    public function connect()
57
    {
58 42
        if ($this->connection === null) {
59 42
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
60
                // @codeCoverageIgnoreStart
61
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
62
                // @codeCoverageIgnoreEnd
63
            }
64
65 42
            $db = null;
66 42
            $options = $this->getOptions();
67
68
            // if port is specified use it, otherwise use the MySQL default
69 42
            if (isset($options['memory'])) {
70
                $dsn = 'sqlite::memory:';
71
            } else {
72 42
                $dsn = 'sqlite:' . $options['name'];
73 42
                if (file_exists($options['name'] . '.sqlite3')) {
74 42
                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
75 42
                }
76
            }
77
78
            try {
79 42
                $db = new \PDO($dsn);
80 42
            } catch (\PDOException $exception) {
81
                throw new \InvalidArgumentException(sprintf(
82
                    'There was a problem connecting to the database: %s',
83
                    $exception->getMessage()
84
                ));
85
            }
86
87 42
            $this->setConnection($db);
88 42
        }
89 42
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 48
    public function disconnect()
95
    {
96 48
        $this->connection = null;
97 48
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function hasTransactions()
103
    {
104
        return true;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 1
    public function beginTransaction()
111
    {
112 1
        $this->execute('BEGIN TRANSACTION');
113 1
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function commitTransaction()
119
    {
120
        $this->execute('COMMIT');
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function rollbackTransaction()
127
    {
128
        $this->execute('ROLLBACK');
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 43
    public function quoteTableName($tableName)
135
    {
136 43
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 44
    public function quoteColumnName($columnName)
143
    {
144 44
        return '`' . str_replace('`', '``', $columnName) . '`';
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 42
    public function hasTable($tableName)
151
    {
152 42
        $tables = [];
153 42
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
154 42
        foreach ($rows as $row) {
155 12
            $tables[] = strtolower($row[0]);
156 42
        }
157
158 42
        return in_array(strtolower($tableName), $tables);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 42
    public function createTable(Table $table)
165
    {
166
        // Add the default primary key
167 42
        $columns = $table->getPendingColumns();
168 42
        $options = $table->getOptions();
169 42
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
170 35
            $column = new Column();
171 35
            $column->setName('id')
172 35
                   ->setType('integer')
173 35
                   ->setIdentity(true);
174
175 35
            array_unshift($columns, $column);
176 42
        } elseif (isset($options['id']) && is_string($options['id'])) {
177
            // Handle id => "field_name" to support AUTO_INCREMENT
178 1
            $column = new Column();
179 1
            $column->setName($options['id'])
180 1
                   ->setType('integer')
181 1
                   ->setIdentity(true);
182
183 1
            array_unshift($columns, $column);
184 1
        }
185
186
        $sql = 'CREATE TABLE ';
187 42
        $sql .= $this->quoteTableName($table->getName()) . ' (';
188 42 View Code Duplication
        foreach ($columns as $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...
189 42
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
190 42
        }
191 42
192
        // set the primary key(s)
193 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...
194 42
            $sql = rtrim($sql);
195 42
            $sql .= ' PRIMARY KEY (';
196 42
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
197 42
                $sql .= $this->quoteColumnName($options['primary_key']);
198 42
            } 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...
199 42
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
200
            }
201
            $sql .= ')';
202 1
        } else {
203 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
204 1
        }
205
206 1
        // set the foreign keys
207 1
        $foreignKeys = $table->getForeignKeys();
208 1
        if (!empty($foreignKeys)) {
209 1
            foreach ($foreignKeys as $foreignKey) {
210 1
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
211 1
            }
212 42
        }
213 42
214 37
        $sql = rtrim($sql) . ');';
215
        // execute the sql
216
        $this->execute($sql);
217
218 42
        foreach ($table->getIndexes() as $index) {
219 42
            $this->addIndex($table, $index);
220 1
        }
221 1
    }
222 1
223 1
    /**
224
     * {@inheritdoc}
225 42
     */
226
    public function renameTable($tableName, $newTableName)
227 42
    {
228
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
229 42
    }
230 6
231 42
    /**
232 42
     * {@inheritdoc}
233
     */
234
    public function dropTable($tableName)
235
    {
236
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
237 1
    }
238
239 1
    /**
240 1
     * {@inheritdoc}
241
     */
242
    public function truncateTable($tableName)
243
    {
244
        $sql = sprintf(
245 1
            'DELETE FROM %s',
246
            $this->quoteTableName($tableName)
247 1
        );
248 1
249
        $this->execute($sql);
250
    }
251
252
    /**
253 1
     * {@inheritdoc}
254
     */
255 1
    public function getColumns($tableName)
256 1
    {
257 1
        $columns = [];
258 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
259
260 1
        foreach ($rows as $columnInfo) {
261 1
            $column = new Column();
262
            $type = strtolower($columnInfo['type']);
263
            $column->setName($columnInfo['name'])
264
                   ->setNull($columnInfo['notnull'] !== '1')
265
                   ->setDefault($columnInfo['dflt_value']);
266 1
267
            $phinxType = $this->getPhinxType($type);
268 1
            $column->setType($phinxType['name'])
269 1
                   ->setLimit($phinxType['limit']);
270
271 1
            if ($columnInfo['pk'] == 1) {
272 1
                $column->setIdentity(true);
273 1
            }
274 1
275 1
            $columns[] = $column;
276 1
        }
277
278 1
        return $columns;
279 1
    }
280 1
281
    /**
282 1
     * {@inheritdoc}
283 1
     */
284 1 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...
285
    {
286 1
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
287 1
        foreach ($rows as $column) {
288
            if (strcasecmp($column['name'], $columnName) === 0) {
289 1
                return true;
290
            }
291
        }
292
293
        return false;
294
    }
295 8
296
    /**
297 8
     * {@inheritdoc}
298 8
     */
299 8 View Code Duplication
    public function addColumn(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...
300 7
    {
301
        $sql = sprintf(
302 8
            'ALTER TABLE %s ADD COLUMN %s %s',
303
            $this->quoteTableName($table->getName()),
304 8
            $this->quoteColumnName($column->getName()),
305
            $this->getColumnSqlDefinition($column)
306
        );
307
308
        $this->execute($sql);
309
    }
310 4
311
    /**
312 4
     * {@inheritdoc}
313 4
     */
314 4
    public function renameColumn($tableName, $columnName, $newColumnName)
315 4
    {
316 4
        $tmpTableName = 'tmp_' . $tableName;
317 4
318
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
319 4
320 4
        $sql = '';
321
        foreach ($rows as $table) {
322
            if ($table['tbl_name'] === $tableName) {
323
                $sql = $table['sql'];
324
            }
325 2
        }
326
327 2
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
328
        $selectColumns = [];
329 2
        $writeColumns = [];
330 View Code Duplication
        foreach ($columns as $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...
331 2
            $selectName = $column['name'];
332 2
            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
333 2
            $selectColumns[] = $this->quoteColumnName($selectName);
334 2
            $writeColumns[] = $this->quoteColumnName($writeName);
335 2
        }
336 2
337 View Code Duplication
        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
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...
338 2
            throw new \InvalidArgumentException(sprintf(
339 2
                'The specified column doesn\'t exist: ' . $columnName
340 2
            ));
341 2
        }
342 2
343 2
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
344 2
345 2
        $sql = str_replace(
346 2
            $this->quoteColumnName($columnName),
347
            $this->quoteColumnName($newColumnName),
348 2
            $sql
349 1
        );
350
        $this->execute($sql);
351 1
352
        $sql = sprintf(
353
            'INSERT INTO %s(%s) SELECT %s FROM %s',
354 1
            $tableName,
355
            implode(', ', $writeColumns),
356 1
            implode(', ', $selectColumns),
357 1
            $tmpTableName
358 1
        );
359
360 1
        $this->execute($sql);
361 1
362
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
363
    }
364 1
365 1
    /**
366 1
     * {@inheritdoc}
367 1
     */
368 1
    public function changeColumn($tableName, $columnName, Column $newColumn)
369
    {
370 1
        // TODO: DRY this up....
371
        $tmpTableName = 'tmp_' . $tableName;
372 1
373
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
374 1
375 1
        $sql = '';
376
        foreach ($rows as $table) {
377
            if ($table['tbl_name'] === $tableName) {
378
                $sql = $table['sql'];
379
            }
380 6
        }
381
382
        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
383
        $selectColumns = [];
384 6
        $writeColumns = [];
385 View Code Duplication
        foreach ($columns as $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...
386 6
            $selectName = $column['name'];
387
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
388 6
            $selectColumns[] = $this->quoteColumnName($selectName);
389 6
            $writeColumns[] = $this->quoteColumnName($writeName);
390 6
        }
391 6
392 6 View Code Duplication
        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
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...
393 6
            throw new \InvalidArgumentException(sprintf(
394
                'The specified column doesn\'t exist: ' . $columnName
395 6
            ));
396 6
        }
397 6
398 6
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
399 6
400 6
        $sql = preg_replace(
401 6
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
402 6
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
403 6
            $sql,
404
            1
405 6
        );
406
407
        $this->execute($sql);
408
409
        $sql = sprintf(
410
            'INSERT INTO %s(%s) SELECT %s FROM %s',
411 6
            $tableName,
412
            implode(', ', $writeColumns),
413 6
            implode(', ', $selectColumns),
414 6
            $tmpTableName
415 6
        );
416 6
417
        $this->execute($sql);
418 6
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
419
    }
420 6
421
    /**
422 6
     * {@inheritdoc}
423 6
     */
424 6
    public function dropColumn($tableName, $columnName)
425 6
    {
426 6
        // TODO: DRY this up....
427
        $tmpTableName = 'tmp_' . $tableName;
428 6
429
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
430 6
431 6
        $sql = '';
432 6
        foreach ($rows as $table) {
433
            if ($table['tbl_name'] === $tableName) {
434
                $sql = $table['sql'];
435
            }
436
        }
437 2
438
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
439
        $columns = [];
440 2
        $columnType = null;
441
        foreach ($rows as $row) {
442 2
            if ($row['name'] !== $columnName) {
443
                $columns[] = $row['name'];
444 2
            } else {
445 2
                $found = true;
446 2
                $columnType = $row['type'];
447 2
            }
448 2
        }
449 2
450
        if (!isset($found)) {
451 2
            throw new \InvalidArgumentException(sprintf(
452 2
                'The specified column doesn\'t exist: ' . $columnName
453 2
            ));
454 2
        }
455 2
456 2
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
457 2
458 2
        $sql = preg_replace(
459 2
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
460
            "",
461 2
            $sql
462
        );
463 2
464
        if (substr($sql, -2) === ', ') {
465
            $sql = substr($sql, 0, -2) . ')';
466
        }
467
468
        $this->execute($sql);
469 2
470
        $sql = sprintf(
471 2
            'INSERT INTO %s(%s) SELECT %s FROM %s',
472 2
            $tableName,
473 2
            implode(', ', $columns),
474
            implode(', ', $columns),
475 2
            $tmpTableName
476
        );
477 2
478 2
        $this->execute($sql);
479 2
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
480
    }
481 2
482
    /**
483 2
     * Get an array of indexes from a particular table.
484 2
     *
485 2
     * @param string $tableName Table Name
486 2
     * @return array
487 2
     */
488
    protected function getIndexes($tableName)
489 2
    {
490
        $indexes = [];
491 2
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
492 2
493 2
        foreach ($rows as $row) {
494
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
495
            if (!isset($indexes[$tableName])) {
496
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
497
            }
498
            foreach ($indexData as $indexItem) {
499
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
500
            }
501 9
        }
502
503 9
        return $indexes;
504 9
    }
505
506 9
    /**
507 9
     * {@inheritdoc}
508 9
     */
509 9 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...
510 9
    {
511 9
        if (is_string($columns)) {
512 9
            $columns = [$columns]; // str to array
513 9
        }
514 9
515 9
        $columns = array_map('strtolower', $columns);
516
        $indexes = $this->getIndexes($tableName);
517
518
        foreach ($indexes as $index) {
519
            $a = array_diff($columns, $index['columns']);
520
            if (empty($a)) {
521 9
                return true;
522
            }
523 9
        }
524 4
525 4
        return false;
526
    }
527 9
528 9
    /**
529
     * {@inheritdoc}
530 9
     */
531 9 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...
532 9
    {
533 9
        $indexes = $this->getIndexes($tableName);
534
535 8
        foreach ($indexes as $index) {
536
            if ($indexName === $index['index']) {
537 8
                return true;
538
            }
539
        }
540
541
        return false;
542
    }
543 1
544
    /**
545 1
     * {@inheritdoc}
546
     */
547 1
    public function addIndex(Table $table, Index $index)
548 1
    {
549 1
        $indexColumnArray = [];
550
        foreach ($index->getColumns() as $column) {
551
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
552
        }
553
        $indexColumns = implode(',', $indexColumnArray);
554
        $this->execute(
555
            sprintf(
556
                'CREATE %s ON %s (%s)',
557
                $this->getIndexSqlDefinition($table, $index),
558
                $this->quoteTableName($table->getName()),
559 8
                $indexColumns
560
            )
561 8
        );
562 8
    }
563 8
564 8
    /**
565 8
     * {@inheritdoc}
566 8
     */
567 8
    public function dropIndex($tableName, $columns)
568 8
    {
569 8
        if (is_string($columns)) {
570 8
            $columns = [$columns]; // str to array
571
        }
572 8
573 8
        $indexes = $this->getIndexes($tableName);
574 8
        $columns = array_map('strtolower', $columns);
575
576
        foreach ($indexes as $index) {
577
            $a = array_diff($columns, $index['columns']);
578
            if (empty($a)) {
579 1
                $this->execute(
580
                    sprintf(
581 1
                        'DROP INDEX %s',
582 1
                        $this->quoteColumnName($index['index'])
583 1
                    )
584
                );
585 1
586 1
                return;
587
            }
588 1
        }
589 1
    }
590 1
591 1
    /**
592 1
     * {@inheritdoc}
593 1
     */
594 1 View Code Duplication
    public function dropIndexByName($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...
595 1
    {
596 1
        $indexes = $this->getIndexes($tableName);
597 1
598
        foreach ($indexes as $index) {
599
            if ($indexName === $index['index']) {
600
                $this->execute(
601
                    sprintf(
602
                        'DROP INDEX %s',
603
                        $this->quoteColumnName($indexName)
604
                    )
605 1
                );
606
607 1
                return;
608
            }
609 1
        }
610 1
    }
611 1
612 1
    /**
613 1
     * {@inheritdoc}
614 1
     */
615 1
    public function hasForeignKey($tableName, $columns, $constraint = null)
616 1
    {
617 1
        if (is_string($columns)) {
618
            $columns = [$columns]; // str to array
619
        }
620
        $foreignKeys = $this->getForeignKeys($tableName);
621
622
        $a = array_diff($columns, $foreignKeys);
623
        if (empty($a)) {
624
            return true;
625 5
        }
626
627 5
        return false;
628
    }
629
630 5
    /**
631
     * Get an array of foreign keys from a particular table.
632 5
     *
633 5
     * @param string $tableName Table Name
634 5
     * @return array
635
     */
636 1
    protected function getForeignKeys($tableName)
637
    {
638
        $foreignKeys = [];
639
        $rows = $this->fetchAll(
640
            "SELECT sql, tbl_name
641
              FROM (
642
                    SELECT sql sql, type type, tbl_name tbl_name, name name
643
                      FROM sqlite_master
644
                     UNION ALL
645 5
                    SELECT sql, type, tbl_name, name
646
                      FROM sqlite_temp_master
647 5
                   )
648 5
             WHERE type != 'meta'
649
               AND sql NOTNULL
650
               AND name NOT LIKE 'sqlite_%'
651
             ORDER BY substr(type, 2, 1), name"
652
        );
653
654
        foreach ($rows as $row) {
655
            if ($row['tbl_name'] === $tableName) {
656
                if (strpos($row['sql'], 'REFERENCES') !== false) {
657
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
658
                    foreach ($matches[1] as $match) {
659
                        $foreignKeys[] = $match;
660
                    }
661 5
                }
662
            }
663 5
        }
664 5
665 5
        return $foreignKeys;
666 5
    }
667 5
668 5
    /**
669 5
     * {@inheritdoc}
670 5
     */
671 5
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
672 5
    {
673 5
        // TODO: DRY this up....
674
        $this->execute('pragma foreign_keys = ON');
675
676
        $tmpTableName = 'tmp_' . $table->getName();
677
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
678
679 4
        $sql = '';
680
        foreach ($rows as $row) {
681
            if ($row['tbl_name'] === $table->getName()) {
682 4
                $sql = $row['sql'];
683
            }
684 4
        }
685 4
686
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
687 4
        $columns = [];
688 4
        foreach ($rows as $column) {
689 4
            $columns[] = $this->quoteColumnName($column['name']);
690 4
        }
691 4
692 4
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));
693
694 4
        $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
695 4
        $this->execute($sql);
696 4
697 4
        $sql = sprintf(
698 4
            'INSERT INTO %s(%s) SELECT %s FROM %s',
699
            $this->quoteTableName($table->getName()),
700 4
            implode(', ', $columns),
701
            implode(', ', $columns),
702 4
            $this->quoteTableName($tmpTableName)
703 4
        );
704
705 4
        $this->execute($sql);
706 4
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
707 4
    }
708 4
709 4
    /**
710 4
     * {@inheritdoc}
711 4
     */
712
    public function dropForeignKey($tableName, $columns, $constraint = null)
713 4
    {
714 4
        // TODO: DRY this up....
715 4
        if (is_string($columns)) {
716
            $columns = [$columns]; // str to array
717
        }
718
719
        $tmpTableName = 'tmp_' . $tableName;
720 1
721
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
722
723 1
        $sql = '';
724
        foreach ($rows as $table) {
725
            if ($table['tbl_name'] === $tableName) {
726
                $sql = $table['sql'];
727 1
            }
728
        }
729 1
730
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
731 1
        $replaceColumns = [];
732 1
        foreach ($rows as $row) {
733 1
            if (!in_array($row['name'], $columns)) {
734 1
                $replaceColumns[] = $row['name'];
735 1
            } else {
736 1
                $found = true;
737
            }
738 1
        }
739 1
740 1
        if (!isset($found)) {
741 1
            throw new \InvalidArgumentException(sprintf(
742 1
                'The specified column doesn\'t exist: '
743 1
            ));
744 1
        }
745
746 1
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));
747
748 1
        foreach ($columns as $columnName) {
749
            $search = sprintf(
750
                "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
751
                $this->quoteColumnName($columnName)
752
            );
753
            $sql = preg_replace($search, '', $sql, 1);
754 1
        }
755
756 1
        $this->execute($sql);
757 1
758 1
        $sql = sprintf(
759 1
            'INSERT INTO %s(%s) SELECT %s FROM %s',
760 1
            $tableName,
761 1
            implode(', ', $columns),
762 1
            implode(', ', $columns),
763
            $tmpTableName
764 1
        );
765
766 1
        $this->execute($sql);
767 1
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
768 1
    }
769 1
770 1
    /**
771
     * {@inheritdoc}
772 1
     */
773
    public function insert(Table $table, $row)
774 1
    {
775 1
        $sql = sprintf(
776 1
            "INSERT INTO %s ",
777
            $this->quoteTableName($table->getName())
778
        );
779
780
        $columns = array_keys($row);
781
        $sql .= "(" . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ")";
782
        $sql .= " VALUES ";
783
784
        $sql .= "(" . implode(', ', array_map(function ($value) {
785
            if (is_numeric($value)) {
786
                return $value;
787
            }
788
789
            if ($value === null) {
790
                return 'null';
791
            }
792
793
                return $this->getConnection()->quote($value);
794
        }, $row)) . ")";
795
796
        $this->execute($sql);
797
    }
798
799
    /**
800
     * {@inheritdoc}
801
     */
802
    public function getSqlType($type, $limit = null)
803
    {
804
        switch ($type) {
805
            case static::PHINX_TYPE_STRING:
806
                return ['name' => 'varchar', 'limit' => 255];
807
            case static::PHINX_TYPE_CHAR:
808
                return ['name' => 'char', 'limit' => 255];
809
            case static::PHINX_TYPE_TEXT:
810 43
                return ['name' => 'text'];
811
            case static::PHINX_TYPE_INTEGER:
812
                return ['name' => 'integer'];
813 43
            case static::PHINX_TYPE_BIG_INTEGER:
814 42
                return ['name' => 'bigint'];
815
            case static::PHINX_TYPE_FLOAT:
816 43
                return ['name' => 'float'];
817
            case static::PHINX_TYPE_DECIMAL:
818
                return ['name' => 'decimal'];
819 43
            case static::PHINX_TYPE_DATETIME:
820 1
                return ['name' => 'datetime'];
821
            case static::PHINX_TYPE_TIMESTAMP:
822 43
                return ['name' => 'datetime'];
823 38
            case static::PHINX_TYPE_TIME:
824
                return ['name' => 'time'];
825 43
            case static::PHINX_TYPE_DATE:
826 42
                return ['name' => 'date'];
827
            case static::PHINX_TYPE_BLOB:
828 43
            case static::PHINX_TYPE_BINARY:
829 2
                return ['name' => 'blob'];
830
            case static::PHINX_TYPE_BOOLEAN:
831 43
                return ['name' => 'boolean'];
832 1
            case static::PHINX_TYPE_UUID:
833
                return ['name' => 'char', 'limit' => 36];
834 43
            case static::PHINX_TYPE_ENUM:
835 1
                return ['name' => 'enum'];
836
            // Geospatial database types
837 43
            // No specific data types exist in SQLite, instead all geospatial
838 42
            // functionality is handled in the client. See also: SpatiaLite.
839
            case static::PHINX_TYPE_GEOMETRY:
840 43
            case static::PHINX_TYPE_POLYGON:
841 1
                return ['name' => 'text'];
842
            case static::PHINX_TYPE_LINESTRING:
843 43
                return ['name' => 'varchar', 'limit' => 255];
844 1
            case static::PHINX_TYPE_POINT:
845
                return ['name' => 'float'];
846 43
            default:
847 43
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
848 1
        }
849
    }
850 43
851 42
    /**
852
     * Returns Phinx type by SQL type
853 5
     *
854
     * @param string $sqlTypeDef SQL type
855 5
     * @returns string Phinx type
856 4
     */
857
    public function getPhinxType($sqlTypeDef)
858
    {
859
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
860 1
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
861 1
        } else {
862
            $limit = null;
863
            $precision = null;
864 1
            $type = $matches[1];
865
            if (count($matches) > 2) {
866
                $limit = $matches[3] ?: null;
867 1
            }
868
            if (count($matches) > 4) {
869 1
                $precision = $matches[5];
870 1
            }
871 1
            switch ($matches[1]) {
872
                case 'varchar':
873
                    $type = static::PHINX_TYPE_STRING;
874
                    if ($limit === 255) {
875
                        $limit = null;
876
                    }
877
                    break;
878 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...
879
                    $type = static::PHINX_TYPE_CHAR;
880 3
                    if ($limit === 255) {
881
                        $limit = null;
882 3
                    }
883 1
                    if ($limit === 36) {
884
                        $type = static::PHINX_TYPE_UUID;
885 2
                    }
886 2
                    break;
887 2
                case 'int':
888 2
                    $type = static::PHINX_TYPE_INTEGER;
889 1
                    if ($limit === 11) {
890 1
                        $limit = null;
891 2
                    }
892
                    break;
893
                case 'bigint':
894 2
                    if ($limit === 11) {
895 2
                        $limit = null;
896 1
                    }
897 1
                    $type = static::PHINX_TYPE_BIG_INTEGER;
898
                    break;
899
                case 'blob':
900 1
                    $type = static::PHINX_TYPE_BINARY;
901 2
                    break;
902
            }
903 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...
904
                if ($matches[3] === 1) {
905
                    $type = static::PHINX_TYPE_BOOLEAN;
906
                    $limit = null;
907
                }
908
            }
909
910 2
            $this->getSqlType($type);
911
912
            return [
913
                'name' => $type,
914
                'limit' => $limit,
915
                'precision' => $precision
916 2
            ];
917 1
        }
918
    }
919
920 1
    /**
921 1
     * {@inheritdoc}
922 2
     */
923 1
    public function createDatabase($name, $options = [])
924 1
    {
925 2
        touch($name . '.sqlite3');
926 2
    }
927
928
    /**
929
     * {@inheritdoc}
930
     */
931
    public function hasDatabase($name)
932
    {
933 2
        return is_file($name . '.sqlite3');
934
    }
935
936 1
    /**
937 1
     * {@inheritdoc}
938
     */
939 1
    public function dropDatabase($name)
940
    {
941
        if (file_exists($name . '.sqlite3')) {
942
            unlink($name . '.sqlite3');
943
        }
944
    }
945
946 48
    /**
947
     * Gets the SQLite Column Definition for a Column object.
948 48
     *
949 48
     * @param \Phinx\Db\Table\Column $column Column
950
     * @return string
951
     */
952
    protected function getColumnSqlDefinition(Column $column)
953
    {
954 2
        if ($column->getType() instanceof Literal) {
0 ignored issues
show
Bug introduced by
The class Phinx\Db\Adapter\Literal does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
955
            $def = (string)$column->getType();
956 2
        } else {
957
            $sqlType = $this->getSqlType($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\SQLiteAdapter::getSqlType() 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...
958
            $def = strtoupper($sqlType['name']);
959
        }
960 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...
961
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
962 48
        }
963
        $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
0 ignored issues
show
Bug introduced by
The variable $sqlType does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
964 48
        if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
965 47
            $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
966 47
        }
967 48 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...
968
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
969
        }
970
971
        $default = $column->getDefault();
972
973
        $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
974
        $def .= $this->getDefaultValueDefinition($default);
975 42
        if (!$isCustomColumn) {
0 ignored issues
show
Bug introduced by
The variable $isCustomColumn does not exist. Did you mean $column?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
976
            $def .= $column->isIdentity() ? ' PRIMARY KEY AUTOINCREMENT' : '';
977 42
        }
978 8
979 42
        if ($column->getUpdate()) {
980 42
            $def .= ' ON UPDATE ' . $column->getUpdate();
981 42
        }
982 42
983
        $def .= $this->getCommentDefinition($column);
984
985
        return $def;
986
    }
987
988
    /**
989
     * Gets the comment Definition for a Column object.
990
     *
991 42
     * @param \Phinx\Db\Table\Column $column Column
992
     * @return string
993 42
     */
994 42
    protected function getCommentDefinition(Column $column)
995 42
    {
996 42
        if ($column->getComment()) {
997
            return ' /* ' . $column->getComment() . ' */ ';
998
        }
999 42
1000 42
        return '';
1001 42
    }
1002 42
1003 42
    /**
1004 4
     * Gets the SQLite Index Definition for an Index object.
1005 4
     *
1006
     * @param \Phinx\Db\Table $table Table
1007 42
     * @param \Phinx\Db\Table\Index $index Index
1008
     * @return string
1009 42
     */
1010 42
    protected function getIndexSqlDefinition(Table $table, Index $index)
1011 42
    {
1012
        if ($index->getType() === Index::UNIQUE) {
1013 42
            $def = 'UNIQUE INDEX';
1014
        } else {
1015
            $def = 'INDEX';
1016
        }
1017 42
        if (is_string($index->getName())) {
1018
            $indexName = $index->getName();
1019 42
        } else {
1020
            $indexName = $table->getName() . '_';
1021
            foreach ($index->getColumns() as $column) {
1022
                $indexName .= $column . '_';
1023
            }
1024
            $indexName .= 'index';
1025
        }
1026
        $def .= ' `' . $indexName . '`';
1027
1028 42
        return $def;
1029
    }
1030 42
1031 2
    /**
1032
     * {@inheritdoc}
1033 42
     */
1034
    public function getColumnTypes()
1035
    {
1036
        return array_merge(parent::getColumnTypes(), ['enum']);
1037
    }
1038
1039
    /**
1040
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
1041
     *
1042 8
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1043
     * @return string
1044 8
     */
1045 2 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...
1046 2
    {
1047 6
        $def = '';
1048
        if ($foreignKey->getConstraint()) {
1049 8
            $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...
1050 3
        } else {
1051 3
            $columnNames = [];
1052 6
            foreach ($foreignKey->getColumns() as $column) {
1053 6
                $columnNames[] = $this->quoteColumnName($column);
1054 6
            }
1055 6
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1056 6
            $refColumnNames = [];
1057
            foreach ($foreignKey->getReferencedColumns() as $column) {
1058 8
                $refColumnNames[] = $this->quoteColumnName($column);
1059 8
            }
1060
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1061
            if ($foreignKey->getOnDelete()) {
1062
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1063
            }
1064
            if ($foreignKey->getOnUpdate()) {
1065 47
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1066
            }
1067 47
        }
1068
1069
        return $def;
1070
    }
1071
}
1072