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

MysqlAdapter::describeTable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 16
loc 16
rs 9.7333
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 1
nc 1
nop 1
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\Mysql as MysqlDriver;
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 MySQL Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 */
45
class MysqlAdapter extends PdoAdapter implements AdapterInterface
46
{
47
48
    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true];
49
50
    const TEXT_TINY = 255;
51
    const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
52
    const TEXT_REGULAR = 65535;
53
    const TEXT_MEDIUM = 16777215;
54
    const TEXT_LONG = 4294967295;
55
56
    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
57
    const BLOB_TINY = 255;
58
    const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
59
    const BLOB_REGULAR = 65535;
60
    const BLOB_MEDIUM = 16777215;
61
    const BLOB_LONG = 4294967295;
62
63
    const INT_TINY = 255;
64
    const INT_SMALL = 65535;
65
    const INT_MEDIUM = 16777215;
66
    const INT_REGULAR = 4294967295;
67
    const INT_BIG = 18446744073709551615;
68
69
    const BIT = 64;
70 80
71
    const TYPE_YEAR = 'year';
72 80
73 80
    /**
74
     * {@inheritdoc}
75
     */
76
    public function connect()
77
    {
78
        if ($this->connection === null) {
79 80
            if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
80 80
                // @codeCoverageIgnoreStart
81
                throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
82 80
                // @codeCoverageIgnoreEnd
83
            }
84 80
85
            $db = null;
86
            $options = $this->getOptions();
87
88
            $dsn = 'mysql:';
89 80
90 80
            if (!empty($options['unix_socket'])) {
91 80
                // use socket connection
92 80
                $dsn .= 'unix_socket=' . $options['unix_socket'];
93
            } else {
94
                // use network connection
95 80
                $dsn .= 'host=' . $options['host'];
96
                if (!empty($options['port'])) {
97
                    $dsn .= ';port=' . $options['port'];
98 80
                }
99
            }
100
101
            $dsn .= ';dbname=' . $options['name'];
102 80
103
            // charset support
104
            if (!empty($options['charset'])) {
105
                $dsn .= ';charset=' . $options['charset'];
106 80
            }
107 80
108
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
109
110 80
            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
111
            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
112 View Code Duplication
            foreach ($options as $key => $option) {
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...
113 80
                if (strpos($key, 'mysql_attr_') === 0) {
114 80
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
115 1
                }
116 1
            }
117 1
118 1
            try {
119
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
120
            } catch (\PDOException $exception) {
121 80
                throw new \InvalidArgumentException(sprintf(
122 80
                    'There was a problem connecting to the database: %s',
123 80
                    $exception->getMessage()
124
                ));
125
            }
126
127
            $this->setConnection($db);
128 81
        }
129
    }
130 81
131 81
    /**
132
     * {@inheritdoc}
133
     */
134
    public function disconnect()
135
    {
136 6
        $this->connection = null;
137
    }
138 6
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function hasTransactions()
143
    {
144 6
        return true;
145
    }
146 6
147 6
    /**
148
     * {@inheritdoc}
149
     */
150
    public function beginTransaction()
151
    {
152 6
        $this->execute('START TRANSACTION');
153
    }
154 6
155 6
    /**
156
     * {@inheritdoc}
157
     */
158
    public function commitTransaction()
159
    {
160 1
        $this->execute('COMMIT');
161
    }
162 1
163 1
    /**
164
     * {@inheritdoc}
165
     */
166
    public function rollbackTransaction()
167
    {
168 112
        $this->execute('ROLLBACK');
169
    }
170 112
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function quoteTableName($tableName)
175
    {
176 112
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
177
    }
178 112
179
    /**
180
     * {@inheritdoc}
181
     */
182
    public function quoteColumnName($columnName)
183
    {
184 82
        return '`' . str_replace('`', '``', $columnName) . '`';
185
    }
186 82
187
    /**
188 82
     * {@inheritdoc}
189
     */
190 View Code Duplication
    public function hasTable($tableName)
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...
191 82
    {
192 82
        $options = $this->getOptions();
193
194 82
        $exists = $this->fetchRow(sprintf(
195
            "SELECT TABLE_NAME
196 82
            FROM INFORMATION_SCHEMA.TABLES
197
            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
198
            $options['name'],
199
            $tableName
200
        ));
201
202 82
        return !empty($exists);
203
    }
204
205
    /**
206 82
     * {@inheritdoc}
207
     */
208 82
    public function createTable(Table $table, array $columns = [], array $indexes = [])
209 82
    {
210
        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
211
        $defaultOptions = [
212 82
            'engine' => 'InnoDB',
213 82
            'collation' => 'utf8_general_ci'
214 68
        ];
215 68
216 68
        $options = array_merge(
217 68
            $defaultOptions,
218 68
            array_intersect_key($this->getOptions(), $defaultOptions),
219
            $table->getOptions()
220 68
        );
221 68
222 82
        // Add the default primary key
223
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
224 2
            $column = new Column();
225 2
            $column->setName('id')
226 2
                   ->setType('integer')
227 2
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
228
                   ->setIdentity(true);
229 2
230 2
            array_unshift($columns, $column);
231 2
            $options['primary_key'] = 'id';
232
        } elseif (isset($options['id']) && is_string($options['id'])) {
233
            // Handle id => "field_name" to support AUTO_INCREMENT
234
            $column = new Column();
235
            $column->setName($options['id'])
236 82
                   ->setType('integer')
237 82
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
238 82
                   ->setIdentity(true);
239 82
240
            array_unshift($columns, $column);
241
            $options['primary_key'] = $options['id'];
242 82
        }
243 82
244 82
        // TODO - process table options like collation etc
245 82
246 82
        // process table engine (default to InnoDB)
247
        $optionsStr = 'ENGINE = InnoDB';
248
        if (isset($options['engine'])) {
249 82
            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
250 2
        }
251 2
252
        // process table collation
253 82 View Code Duplication
        if (isset($options['collation'])) {
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...
254 82
            $charset = explode('_', $options['collation']);
255 82
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
256 82
            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
257 82
        }
258
259
        // set the table comment
260 82
        if (isset($options['comment'])) {
261 82
            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
262 82
        }
263 82
264 81
        // set the table row format
265 82
        if (isset($options['row_format'])) {
266
            $optionsStr .= sprintf(" ROW_FORMAT=%s ", $options['row_format']);
267
        }
268 2
269 2
        $sql = 'CREATE TABLE ';
270 2
        $sql .= $this->quoteTableName($table->getName()) . ' (';
271 2
        foreach ($columns as $column) {
272 2
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
273 2
        }
274 2
275 2
        // set the primary key(s)
276 2 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...
277 2
            $sql = rtrim($sql);
278 82
            $sql .= ' PRIMARY KEY (';
279 82
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
280 1
                $sql .= $this->quoteColumnName($options['primary_key']);
281
            } 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...
282
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
283
            }
284 82
            $sql .= ')';
285 82
        } else {
286 10
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
287 82
        }
288
289
        // set the indexes
290 82
        foreach ($indexes as $index) {
291 82
            $sql .= ', ' . $this->getIndexSqlDefinition($index);
292 2
        }
293 82
294
        $sql .= ') ' . $optionsStr;
295 82
        $sql = rtrim($sql) . ';';
296 82
297
        // execute the sql
298
        $this->execute($sql);
299 82
    }
300 82
301
    /**
302
     * {@inheritdoc}
303
     */
304
    protected function getChangeTableInstructions(Table $table, array $newOptions)
305 5
    {
306
        $instructions = new AlterInstructions();
307 5
308 5
        // Drop the existing primary key
309
        $primaryKey = $this->getPrimaryKey($table->getName());
310
        if ((isset($newOptions['id']) || isset($newOptions['primary_key']))
311
            && !empty($primaryKey['columns']))
312
        {
313 5
            foreach ($primaryKey['columns'] as $column) {
314
                $sql = sprintf(
315 5
                    'MODIFY %s INT NOT NULL',
316 5
                    $this->quoteColumnName($column)
317
                );
318
                $instructions->addAlter($sql);
319
            }
320
            $instructions->addAlter('DROP PRIMARY KEY');
321 1
        }
322
323 1
        // Set the default primary key and add associated column
324 1
        if (isset($newOptions['id']) && $newOptions['id'] !== false) {
325 1
            if ($newOptions['id'] === true) {
326 1
                $newOptions['primary_key'] = 'id';
327
            } else if (is_string($newOptions['id'])) {
328 1
                // Handle id => "field_name" to support AUTO_INCREMENT
329 1
                $newOptions['primary_key'] = $newOptions['id'];
330
            } else {
331
                throw new \InvalidArgumentException(sprintf(
332
                    "Invalid value for option 'id': %s",
333
                    json_encode($newOptions['id'])
334 12
                ));
335
            }
336 12
337 12
            if ($this->hasColumn($table->getName(), $newOptions['primary_key'])) {
338 12
                throw new \RuntimeException(sprintf(
339 12
                    "Tried to create primary key column %s for table %s, but that column already exists",
340
                    $this->quoteColumnName($newOptions['primary_key']),
341 12
                    $this->quoteTableName($table->getName())
342 12
                ));
343 12
            }
344 12
345 12
            $column = new Column();
346 12
            $column
347
                ->setName($newOptions['primary_key'])
348 12
                ->setType('integer')
349 12
                ->setSigned(isset($newOptions['signed']) ? $newOptions['signed'] : true)
350 12
                ->setIdentity(true);
351
            $instructions->merge($this->getAddColumnInstructions($table, $column));
352 12
        }
353 3
354 3
        // Add the primary key(s)
355
        if (isset($newOptions['primary_key']) && $newOptions['primary_key'] !== false) {
356 12
            $sql = 'ADD PRIMARY KEY (';
357 12
            if (is_string($newOptions['primary_key'])) { // handle primary_key => 'id'
358
                $sql .= $this->quoteColumnName($newOptions['primary_key']);
359 12
            } else if (is_array($newOptions['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...
360
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newOptions['primary_key']));
361
            } else {
362
                throw new \InvalidArgumentException(sprintf(
363
                    "Invalid value for option 'primary_key': %s",
364
                    json_encode($newOptions['primary_key'])
365 79
                ));
366
            }
367 79
            $sql .= ')';
368 79
            $instructions->addAlter($sql);
369 79
        }
370 77
371
        // process table engine (default to InnoDB)
372 77
        $optionsStr = 'ENGINE = InnoDB';
373
        if (isset($newOptions['engine'])) {
374 21
            $optionsStr = sprintf('ENGINE = %s', $newOptions['engine']);
375
        }
376
        // process table collation
377 View Code Duplication
        if (isset($newOptions['collation'])) {
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...
378
            $charset = explode('_', $newOptions['collation']);
379
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
380
            $optionsStr .= sprintf(' COLLATE %s', $newOptions['collation']);
381
        }
382
        // set the table comment
383 95
        if (array_key_exists('comment', $newOptions)) {
384
            // passing 'null' is to remove table comment
385 95
            $newComment = ($newOptions['comment'] !== null)
386 10
                ? $newOptions['comment']
387 95
                : '';
388 79
            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($newComment));
389 79
        }
390 95
        // set the table row format
391
        if (isset($newOptions['row_format'])) {
392
            $optionsStr .= sprintf(" ROW_FORMAT=%s ", $newOptions['row_format']);
393
        }
394
        $instructions->addAlter($optionsStr);
395
396 18
        return $instructions;
397
    }
398 18
399 18
    /**
400 18
     * {@inheritdoc}
401 18
     */
402 18 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...
403 18
    {
404
        $sql = sprintf(
405 18
            'RENAME TABLE %s TO %s',
406 2
            $this->quoteTableName($tableName),
407 2
            $this->quoteTableName($newTableName)
408
        );
409 18
410 18
        return new AlterInstructions([], [$sql]);
411
    }
412
413
    /**
414
     * {@inheritdoc}
415 7
     */
416
    protected function getDropTableInstructions($tableName)
417 7
    {
418 7
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
419 7
420 5
        return new AlterInstructions([], [$sql]);
421 5
    }
422 5
423 1
    /**
424 1
     * {@inheritdoc}
425 5
     */
426
    public function truncateTable($tableName)
427 5
    {
428 5
        $sql = sprintf(
429 5
            'TRUNCATE TABLE %s',
430 5
            $this->quoteTableName($tableName)
431 5
        );
432 5
433
        $this->execute($sql);
434 5
    }
435 5
436 5
    /**
437
     * {@inheritdoc}
438 6
     */
439
    public function getColumns($tableName)
440 2
    {
441
        $columns = [];
442
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
443 2
        foreach ($rows as $columnInfo) {
444
            $phinxType = $this->getPhinxType($columnInfo['Type']);
445
446
            $column = new Column();
447
            $column->setName($columnInfo['Field'])
448
                   ->setNull($columnInfo['Null'] !== 'NO')
449 5
                   ->setDefault($columnInfo['Default'])
450
                   ->setType($phinxType['name'])
451 5
                   ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false)
452 5
                   ->setLimit($phinxType['limit']);
453 5
454 5
            if ($columnInfo['Extra'] === 'auto_increment') {
455 5
                $column->setIdentity(true);
456 5
            }
457 5
458 5
            if (isset($phinxType['values'])) {
459
                $column->setValues($phinxType['values']);
460 5
            }
461 5
462 5
            $columns[] = $column;
463
        }
464
465
        return $columns;
466
    }
467 5
468
    /**
469 5
     * {@inheritdoc}
470 5
     */
471 5 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...
472 5
    {
473 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
474 5
        foreach ($rows as $column) {
475 5
            if (strcasecmp($column['Field'], $columnName) === 0) {
476 5
                return true;
477
            }
478
        }
479
480
        return false;
481
    }
482
483
    /**
484 19
     * {@inheritdoc}
485
     */
486 19
    protected function getAddColumnInstructions(Table $table, Column $column)
487 19
    {
488 19
        $alter = sprintf(
489 18
            'ADD %s %s',
490 18
            $this->quoteColumnName($column->getName()),
491 18
            $this->getColumnSqlDefinition($column)
492 18
        );
493 19
494 19
        if ($column->getAfter()) {
495
            $alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
496
        }
497
498
        return new AlterInstructions([$alter]);
499
    }
500 14
501
    /**
502 14
     * {@inheritdoc}
503 6
     */
504 6
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
505
    {
506 14
        $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
507 14
508
        foreach ($rows as $row) {
509 14
            if (strcasecmp($row['Field'], $columnName) === 0) {
510 14
                $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
511 12
                $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
512
                $extra = ' ' . strtoupper($row['Extra']);
513 13
                if (!is_null($row['Default'])) {
514
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
515 11
                }
516
                $definition = $row['Type'] . ' ' . $null . $extra . $comment;
517
518
                $alter = sprintf(
519
                    'CHANGE COLUMN %s %s %s',
520
                    $this->quoteColumnName($columnName),
521 1
                    $this->quoteColumnName($newColumnName),
522
                    $definition
523 1
                );
524
525 1
                return new AlterInstructions([$alter]);
526 1
            }
527 1
        }
528
529 1
        throw new \InvalidArgumentException(sprintf(
530
            'The specified column doesn\'t exist: ' .
531
            $columnName
532
        ));
533
    }
534
535
    /**
536
     * {@inheritdoc}
537 4
     */
538
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
539 4
    {
540 4
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
541 4
        $alter = sprintf(
542 4
            'CHANGE %s %s %s%s',
543 4
            $this->quoteColumnName($columnName),
544 4
            $this->quoteColumnName($newColumn->getName()),
545 4
            $this->getColumnSqlDefinition($newColumn),
546 4
            $after
547
        );
548
549
        return new AlterInstructions([$alter]);
550
    }
551 3
552
    /**
553 3
     * {@inheritdoc}
554 2
     */
555 2
    protected function getDropColumnInstructions($tableName, $columnName)
556
    {
557 3
        $alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName));
558 3
559
        return new AlterInstructions([$alter]);
560 3
    }
561 3
562 3
    /**
563 3
     * Get an array of indexes from a particular table.
564 3
     *
565 3
     * @param string $tableName Table Name
566 3
     * @return array
567 3
     */
568 3
    protected function getIndexes($tableName)
569 3
    {
570
        $indexes = [];
571 3
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
572 1 View Code Duplication
        foreach ($rows as $row) {
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...
573
            if (!isset($indexes[$row['Key_name']])) {
574
                $indexes[$row['Key_name']] = ['columns' => []];
575
            }
576
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
577 2
        }
578
579 2
        return $indexes;
580
    }
581 2
582
    /**
583 2
     * {@inheritdoc}
584 2
     */
585 2 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...
586 2
    {
587 2
        if (is_string($columns)) {
588 2
            $columns = [$columns]; // str to array
589 2
        }
590 2
591 2
        $columns = array_map('strtolower', $columns);
592
        $indexes = $this->getIndexes($tableName);
593 2
594
        foreach ($indexes as $index) {
595
            if ($columns == $index['columns']) {
596
                return true;
597
            }
598
        }
599 21
600
        return false;
601 21
    }
602 5
603 5
    /**
604 21
     * {@inheritdoc}
605 21
     */
606 6 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...
607 4
    {
608
        $indexes = $this->getIndexes($tableName);
609 4
610
        foreach ($indexes as $name => $index) {
611 15
            if ($name === $indexName) {
612 12
                return true;
613 10
            }
614
        }
615 11
616 11
        return false;
617
    }
618
619
    /**
620
     * {@inheritdoc}
621
     */
622
    protected function getAddIndexInstructions(Table $table, Index $index)
623
    {
624
        $alter = sprintf(
625
            'ADD %s',
626 22
            $this->getIndexSqlDefinition($index)
627
        );
628 22
629 22
        return new AlterInstructions([$alter]);
630
    }
631
632
    /**
633
     * {@inheritdoc}
634
     */
635
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
636
    {
637
        if (is_string($columns)) {
638
            $columns = [$columns]; // str to array
639
        }
640 22
641
        $indexes = $this->getIndexes($tableName);
642 22
        $columns = array_map('strtolower', $columns);
643 22
644 19 View Code Duplication
        foreach ($indexes as $indexName => $index) {
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...
645 19
            if ($columns == $index['columns']) {
646 19
                return new AlterInstructions([sprintf(
647 19
                    'DROP INDEX %s',
648 22
                    $this->quoteColumnName($indexName)
649 22
                )]);
650
            }
651
        }
652
653
        throw new \InvalidArgumentException(sprintf(
654
            "The specified index on columns '%s' does not exist",
655 15
            implode(',', $columns)
656
        ));
657 15
    }
658 15
659 15
    /**
660 15
     * {@inheritdoc}
661 15
     */
662 15
    protected function getDropIndexByNameInstructions($tableName, $indexName)
663 15
    {
664 15
665
        $indexes = $this->getIndexes($tableName);
666
667 View Code Duplication
        foreach ($indexes as $name => $index) {
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...
668
            if ($name === $indexName) {
669 8
                return new AlterInstructions([sprintf(
670
                    'DROP INDEX %s',
671 8
                    $this->quoteColumnName($indexName)
672 3
                )]);
673 3
            }
674
        }
675
676 8
        throw new \InvalidArgumentException(sprintf(
677 8
            "The specified index name '%s' does not exist",
678 8
            $indexName
679 8
        ));
680 8
    }
681
682 8
    /**
683 8
     * {@inheritdoc}
684 8
     */
685 View Code Duplication
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
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...
686 7
    {
687 7
        $primaryKey = $this->getPrimaryKey($tableName);
688
689
        if (empty($primaryKey['constraint'])) {
690
            return false;
691
        }
692
693
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
694
            return ($primaryKey['constraint'] === $constraint);
695 7
        } else {
696 7
            if (is_string($columns)) {
697
                $columns = [$columns]; // str to array
698 7
            }
699 7
            $missingColumns = array_diff($columns, $primaryKey['columns']);
700 7
            return empty($missingColumns);
701 7
        }
702 7
    }
703
704 7
    /**
705
     * Get the primary key from a particular table.
706
     *
707
     * @param string $tableName Table Name
708
     * @return array
709 96
     */
710 View Code Duplication
    public function getPrimaryKey($tableName)
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...
711
    {
712 96
        $rows = $this->fetchAll(sprintf(
713 87
            "SELECT
714
                k.constraint_name,
715 96
                k.column_name
716 4
            FROM information_schema.table_constraints t
717
            JOIN information_schema.key_column_usage k
718 96
                USING(constraint_name,table_name)
719 9
            WHERE t.constraint_type='PRIMARY KEY'
720
                AND t.table_name='%s'",
721
            $tableName
722 6
        ));
723 6
724 6
        $primaryKey = [
725 6
            'columns' => [],
726 6
        ];
727 6
        foreach ($rows as $row) {
728 6
            $primaryKey['constraint'] = $row['constraint_name'];
729 6
            $primaryKey['columns'][] = $row['column_name'];
730
        }
731 5
        return $primaryKey;
732
    }
733 5
734
    /**
735 95
     * {@inheritdoc}
736 5
     */
737 View Code Duplication
    public function hasForeignKey($tableName, $columns, $constraint = null)
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...
738 95
    {
739 3
        if (is_string($columns)) {
740
            $columns = [$columns]; // str to array
741 95
        }
742 1
        $foreignKeys = $this->getForeignKeys($tableName);
743
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
744
            if (isset($foreignKeys[$constraint])) {
745 1
                return !empty($foreignKeys[$constraint]);
746 1
            }
747 1
748 1
            return false;
749 1
        } else {
750 1
            foreach ($foreignKeys as $key) {
751 1
                if ($columns == $key['columns']) {
752 1
                    return true;
753
                }
754 1
            }
755
756 1
            return false;
757
        }
758 95
    }
759 82
760
    /**
761
     * Get an array of foreign keys from a particular table.
762 6
     *
763 6
     * @param string $tableName Table Name
764 6
     * @return array
765 6
     */
766 6 View Code Duplication
    protected function getForeignKeys($tableName)
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...
767 6
    {
768
        $foreignKeys = [];
769 6
        $rows = $this->fetchAll(sprintf(
770 6
            "SELECT
771 6
              CONSTRAINT_NAME,
772 6
              TABLE_NAME,
773 6
              COLUMN_NAME,
774 6
              REFERENCED_TABLE_NAME,
775 6
              REFERENCED_COLUMN_NAME
776 2
            FROM information_schema.KEY_COLUMN_USAGE
777 2
            WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
778 6
              AND REFERENCED_TABLE_NAME IS NOT NULL
779
              AND TABLE_NAME = '%s'
780 5
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
781 82
            $tableName
782 76
        ));
783 76
        foreach ($rows as $row) {
784 82
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
785
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
786 86
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
787 82
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
788
        }
789 86
790 7
        return $foreignKeys;
791
    }
792 84
793 5
    /**
794
     * {@inheritdoc}
795 83
     */
796 7 View Code Duplication
    protected function getAddForeignKeyInstructions(Table $table, 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...
797
    {
798 83
        $alter = sprintf(
799 80
            'ADD %s',
800
            $this->getForeignKeySqlDefinition($foreignKey)
801 83
        );
802 4
803
        return new AlterInstructions([$alter]);
804 83
    }
805 4
806
    /**
807 83
     * {@inheritdoc}
808 80
     */
809
    protected function getDropForeignKeyInstructions($tableName, $constraint)
810 10
    {
811 2
        $alter = sprintf(
812
            'DROP FOREIGN KEY %s',
813 10
            $constraint
814 10
        );
815 10
816 10
        return new AlterInstructions([$alter]);
817 5
    }
818 8
819 5
    /**
820
     * {@inheritdoc}
821 6
     */
822 4
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
823
    {
824 2
        $instructions = new AlterInstructions();
825
826 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...
827
            $rows = $this->fetchAll(sprintf(
828
                "SELECT
829
                    CONSTRAINT_NAME
830 2
                  FROM information_schema.KEY_COLUMN_USAGE
831
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
832
                    AND REFERENCED_TABLE_NAME IS NOT NULL
833 2
                    AND TABLE_NAME = '%s'
834 2
                    AND COLUMN_NAME = '%s'
835 2
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
836
                $tableName,
837
                $column
838
            ));
839
840
            foreach ($rows as $row) {
841
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
842
            }
843
        }
844
845
        if (empty($instructions->getAlterParts())) {
846 17
            throw new \InvalidArgumentException(sprintf(
847
                "Not foreign key on columns '%s' exist",
848 17
                implode(',', $columns)
849 17
            ));
850 1
        }
851
852 16
        return $instructions;
853 16
    }
854 16
855 16
    /**
856 14
     * {@inheritdoc}
857 14
     */
858 16
    public function getSqlType($type, $limit = null)
859 4
    {
860 4
        switch ($type) {
861 16
            case static::PHINX_TYPE_FLOAT:
862 3
            case static::PHINX_TYPE_DECIMAL:
863 3
            case static::PHINX_TYPE_DATE:
864 3
            case static::PHINX_TYPE_ENUM:
865
            case static::PHINX_TYPE_SET:
866 16
            case static::PHINX_TYPE_JSON:
867 6
            // Geospatial database types
868 6
            case static::PHINX_TYPE_GEOMETRY:
869 3
            case static::PHINX_TYPE_POINT:
870 3
            case static::PHINX_TYPE_LINESTRING:
871 6
            case static::PHINX_TYPE_POLYGON:
872 16
                return ['name' => $type];
873 5
            case static::PHINX_TYPE_DATETIME:
874 5
            case static::PHINX_TYPE_TIMESTAMP:
875 1
            case static::PHINX_TYPE_TIME:
876 1
                return ['name' => $type, 'limit' => $limit];
877 5
            case static::PHINX_TYPE_STRING:
878 2
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
879 2
            case static::PHINX_TYPE_CHAR:
880 5
                return ['name' => 'char', 'limit' => $limit ?: 255];
881 16 View Code Duplication
            case static::PHINX_TYPE_TEXT:
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...
882 2
                if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
883 2
                    $sizes = [
884 2
                        // Order matters! Size must always be tested from longest to shortest!
885 16
                        'longtext' => static::TEXT_LONG,
886 2
                        'mediumtext' => static::TEXT_MEDIUM,
887 2
                        'text' => static::TEXT_REGULAR,
888 2
                        'tinytext' => static::TEXT_SMALL,
889 16
                    ];
890 2
                    foreach ($sizes as $name => $length) {
891 2
                        if ($limit >= $length) {
892 2
                            return ['name' => $name];
893 16
                        }
894 15
                    }
895 15
                }
896 12
897 12
                return ['name' => 'text'];
898 15
            case static::PHINX_TYPE_BINARY:
899 11
                return ['name' => 'binary', 'limit' => $limit ?: 255];
900 6
            case static::PHINX_TYPE_VARBINARY:
901 4
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
902 4 View Code Duplication
            case static::PHINX_TYPE_BLOB:
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...
903 6
                if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
904 6
                    $sizes = [
905 10
                        // Order matters! Size must always be tested from longest to shortest!
906 2
                        'longblob' => static::BLOB_LONG,
907 2
                        'mediumblob' => static::BLOB_MEDIUM,
908 10
                        'blob' => static::BLOB_REGULAR,
909 1
                        'tinyblob' => static::BLOB_SMALL,
910 1
                    ];
911 1
                    foreach ($sizes as $name => $length) {
912 10
                        if ($limit >= $length) {
913 1
                            return ['name' => $name];
914 1
                        }
915 1
                    }
916 10
                }
917 1
918 1
                return ['name' => 'blob'];
919 1
            case static::PHINX_TYPE_BIT:
920 10
                return ['name' => 'bit', 'limit' => $limit ?: 64];
921 2
            case static::PHINX_TYPE_INTEGER:
922 2
                if ($limit && $limit >= static::INT_TINY) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
923 2
                    $sizes = [
924 9
                        // Order matters! Size must always be tested from longest to shortest!
925 2
                        'bigint' => static::INT_BIG,
926 2
                        'int' => static::INT_REGULAR,
927 2
                        'mediumint' => static::INT_MEDIUM,
928 8
                        'smallint' => static::INT_SMALL,
929 2
                        'tinyint' => static::INT_TINY,
930 2
                    ];
931 2
                    $limits = [
932
                        'int' => 11,
933
                        'bigint' => 20,
934
                    ];
935 16
                    foreach ($sizes as $name => $length) {
936
                        if ($limit >= $length) {
937
                            $def = ['name' => $name];
938 15
                            if (isset($limits[$name])) {
939 15
                                $def['limit'] = $limits[$name];
940
                            }
941 15
942
                            return $def;
943 15
                        }
944 3
                    }
945 3
                } elseif (!$limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
946
                    $limit = 11;
947 15
                }
948
949
                return ['name' => 'int', 'limit' => $limit];
950
            case static::PHINX_TYPE_BIG_INTEGER:
951
                return ['name' => 'bigint', 'limit' => 20];
952
            case static::PHINX_TYPE_BOOLEAN:
953
                return ['name' => 'tinyint', 'limit' => 1];
954 83
            case static::PHINX_TYPE_UUID:
955
                return ['name' => 'char', 'limit' => 36];
956 83
            case static::TYPE_YEAR:
957
                if (!$limit || in_array($limit, [2, 4])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
958 83
                    $limit = 4;
959 1
                }
960 1
961 82
                return ['name' => 'year', 'limit' => $limit];
962
            default:
963 83
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
964
        }
965
    }
966
967
    /**
968 4
     * Returns Phinx type by SQL type
969
     *
970 4
     * @param string $sqlTypeDef
971 4
     * @throws \RuntimeException
972 4
     * @internal param string $sqlType SQL type
973
     * @returns string Phinx type
974 4
     */
975 4
    public function getPhinxType($sqlTypeDef)
976
    {
977 4
        $matches = [];
978 3
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
979 3
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
980
        } else {
981 3
            $limit = null;
982
            $precision = null;
983 3
            $type = $matches[1];
984
            if (count($matches) > 2) {
985
                $limit = $matches[3] ? (int)$matches[3] : null;
986
            }
987
            if (count($matches) > 4) {
988
                $precision = (int)$matches[5];
989 81
            }
990 View Code Duplication
            if ($type === 'tinyint' && $limit === 1) {
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...
991 81
                $type = static::PHINX_TYPE_BOOLEAN;
992 81
                $limit = null;
993
            }
994
            switch ($type) {
995
                case 'varchar':
996
                    $type = static::PHINX_TYPE_STRING;
997
                    if ($limit === 255) {
998
                        $limit = null;
999
                    }
1000 89
                    break;
1001 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...
1002 89
                    $type = static::PHINX_TYPE_CHAR;
1003
                    if ($limit === 255) {
1004 89
                        $limit = null;
1005 89
                    }
1006 89
                    if ($limit === 36) {
1007 2
                        $type = static::PHINX_TYPE_UUID;
1008 89
                    }
1009 86
                    break;
1010 86
                case 'tinyint':
1011 89
                    $type = static::PHINX_TYPE_INTEGER;
1012 5
                    $limit = static::INT_TINY;
1013 5
                    break;
1014 89
                case 'smallint':
1015 89
                    $type = static::PHINX_TYPE_INTEGER;
1016 89
                    $limit = static::INT_SMALL;
1017 89
                    break;
1018 89
                case 'mediumint':
1019 89
                    $type = static::PHINX_TYPE_INTEGER;
1020
                    $limit = static::INT_MEDIUM;
1021 89
                    break;
1022 2
                case 'int':
1023 2
                    $type = static::PHINX_TYPE_INTEGER;
1024
                    if ($limit === 11) {
1025 89
                        $limit = null;
1026 1
                    }
1027 1
                    break;
1028
                case 'bigint':
1029 89
                    if ($limit === 20) {
1030
                        $limit = null;
1031
                    }
1032
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1033
                    break;
1034
                case 'bit':
1035
                    $type = static::PHINX_TYPE_BIT;
1036
                    if ($limit === 64) {
1037
                        $limit = null;
1038 16
                    }
1039
                    break;
1040 16
                case 'blob':
1041 16
                    $type = static::PHINX_TYPE_BINARY;
1042 16
                    break;
1043 2
                case 'tinyblob':
1044 2
                    $type = static::PHINX_TYPE_BINARY;
1045
                    $limit = static::BLOB_TINY;
1046 16
                    break;
1047 5
                case 'mediumblob':
1048 5
                    $type = static::PHINX_TYPE_BINARY;
1049
                    $limit = static::BLOB_MEDIUM;
1050 16
                    break;
1051 1
                case 'longblob':
1052 1
                    $type = static::PHINX_TYPE_BINARY;
1053
                    $limit = static::BLOB_LONG;
1054 16
                    break;
1055
                case 'tinytext':
1056 16
                    $type = static::PHINX_TYPE_TEXT;
1057 5
                    $limit = static::TEXT_TINY;
1058 5
                    break;
1059
                case 'mediumtext':
1060 16
                    $type = static::PHINX_TYPE_TEXT;
1061
                    $limit = static::TEXT_MEDIUM;
1062 16
                    break;
1063
                case 'longtext':
1064
                    $type = static::PHINX_TYPE_TEXT;
1065
                    $limit = static::TEXT_LONG;
1066
                    break;
1067
            }
1068
1069
            // Call this to check if parsed type is supported.
1070
            $this->getSqlType($type, $limit);
1071 17
1072
            $phinxType = [
1073 17
                'name' => $type,
1074 17
                'limit' => $limit,
1075 5
                'precision' => $precision
1076 5
            ];
1077 17
1078 17
            if (static::PHINX_TYPE_ENUM == $type) {
1079 17
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
1080 17
            }
1081 17
1082 17
            return $phinxType;
1083 17
        }
1084 17
    }
1085 17
1086 17
    /**
1087 17
     * {@inheritdoc}
1088 2
     */
1089 2
    public function createDatabase($name, $options = [])
1090 17
    {
1091 2
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
1092 2
1093 17 View Code Duplication
        if (isset($options['collation'])) {
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...
1094
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
1095
        } else {
1096
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1097
        }
1098
    }
1099
1100
    /**
1101
     * {@inheritdoc}
1102 2
     */
1103
    public function hasDatabase($name)
1104 2
    {
1105
        $rows = $this->fetchAll(
1106
            sprintf(
1107 2
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1108
                $name
1109
            )
1110
        );
1111 2
1112 2
        foreach ($rows as $row) {
1113
            if (!empty($row)) {
1114 2
                return true;
1115
            }
1116 2
        }
1117
1118
        return false;
1119
    }
1120
1121
    /**
1122
     * {@inheritdoc}
1123 85
     */
1124
    public function dropDatabase($name)
1125 85
    {
1126
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1127
    }
1128
1129
    /**
1130
     * Gets the MySQL Column Definition for a Column object.
1131
     *
1132
     * @param \Phinx\Db\Table\Column $column Column
1133
     * @return string
1134
     */
1135
    protected function getColumnSqlDefinition(Column $column)
1136
    {
1137
        if ($column->getType() instanceof Literal) {
1138
            $def = (string)$column->getType();
1139
        } else {
1140
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1141
            $def = strtoupper($sqlType['name']);
1142
        }
1143
        if ($column->getPrecision() && $column->getScale()) {
1144
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1145
        } elseif (isset($sqlType['limit'])) {
1146
            $def .= '(' . $sqlType['limit'] . ')';
1147
        }
1148 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...
1149
            $def .= "('" . implode("', '", $values) . "')";
1150
        }
1151
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1152
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1153
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
1154
        $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1155
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1156
        $def .= $this->getDefaultValueDefinition($column->getDefault(), $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...
1157
1158
        if ($column->getComment()) {
1159
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1160
        }
1161
1162
        if ($column->getUpdate()) {
1163
            $def .= ' ON UPDATE ' . $column->getUpdate();
1164
        }
1165
1166
        return $def;
1167
    }
1168
1169
    /**
1170
     * Gets the MySQL Index Definition for an Index object.
1171
     *
1172
     * @param \Phinx\Db\Table\Index $index Index
1173
     * @return string
1174
     */
1175
    protected function getIndexSqlDefinition(Index $index)
1176
    {
1177
        $def = '';
1178
        $limit = '';
1179
1180
        if ($index->getType() == Index::UNIQUE) {
1181
            $def .= ' UNIQUE';
1182
        }
1183
1184
        if ($index->getType() == Index::FULLTEXT) {
1185
            $def .= ' FULLTEXT';
1186
        }
1187
1188
        $def .= ' KEY';
1189
1190
        if (is_string($index->getName())) {
1191
            $def .= ' `' . $index->getName() . '`';
1192
        }
1193
1194
        if (!is_array($index->getLimit())) {
1195
            if ($index->getLimit()) {
1196
                $limit = '(' . $index->getLimit() . ')';
1197
            }
1198
            $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
1199
        } else {
1200
            $columns = $index->getColumns();
1201
            $limits = $index->getLimit();
1202
            $def .= ' (';
1203
            foreach ($columns as $column) {
1204
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1205
                $def .= '`' . $column . '`' . $limit . ', ';
1206
            }
1207
            $def = rtrim($def, ', ');
1208
            $def .= ' )';
1209
        }
1210
1211
        return $def;
1212
    }
1213
1214
    /**
1215
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1216
     *
1217
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1218
     * @return string
1219
     */
1220 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...
1221
    {
1222
        $def = '';
1223
        if ($foreignKey->getConstraint()) {
1224
            $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\MysqlAdapter::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...
1225
        }
1226
        $columnNames = [];
1227
        foreach ($foreignKey->getColumns() as $column) {
1228
            $columnNames[] = $this->quoteColumnName($column);
1229
        }
1230
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1231
        $refColumnNames = [];
1232
        foreach ($foreignKey->getReferencedColumns() as $column) {
1233
            $refColumnNames[] = $this->quoteColumnName($column);
1234
        }
1235
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1236
        if ($foreignKey->getOnDelete()) {
1237
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1238
        }
1239
        if ($foreignKey->getOnUpdate()) {
1240
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1241
        }
1242
1243
        return $def;
1244
    }
1245
1246
    /**
1247
     * Describes a database table. This is a MySQL adapter specific method.
1248
     *
1249
     * @param string $tableName Table name
1250
     * @return array
1251
     */
1252 View Code Duplication
    public function describeTable($tableName)
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...
1253
    {
1254
        $options = $this->getOptions();
1255
1256
        // mysql specific
1257
        $sql = sprintf(
1258
            "SELECT *
1259
             FROM information_schema.tables
1260
             WHERE table_schema = '%s'
1261
             AND table_name = '%s'",
1262
            $options['name'],
1263
            $tableName
1264
        );
1265
1266
        return $this->fetchRow($sql);
1267
    }
1268
1269
    /**
1270
     * Returns MySQL column types (inherited and MySQL specified).
1271
     * @return array
1272
     */
1273
    public function getColumnTypes()
1274
    {
1275
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
1276
    }
1277
1278
    /**
1279
     * {@inheritDoc}
1280
     *
1281
     */
1282 View Code Duplication
    public function getDecoratedConnection()
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...
1283
    {
1284
        $options = $this->getOptions();
1285
        $options = [
1286
            'username' => $options['user'],
1287
            'password' => $options['pass'],
1288
            'database' => $options['name'],
1289
            'quoteIdentifiers' => true,
1290
        ] + $options;
1291
1292
        $driver = new MysqlDriver($options);
1293
        if (method_exists($driver, 'setConnection')) {
1294
            $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...
1295
        } else {
1296
            $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...
1297
        }
1298
1299
        return new Connection(['driver' => $driver] + $options);
1300
    }
1301
}
1302