Completed
Push — master ( 7e48e0...d84a6c )
by Mark
03:04 queued 01:42
created

SqlServerAdapter::createTable()   D

Complexity

Conditions 14
Paths 288

Size

Total Lines 73
Code Lines 44

Duplication

Lines 31
Ratio 42.47 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
dl 31
loc 73
ccs 0
cts 55
cp 0
rs 4.0761
c 0
b 0
f 0
cc 14
eloc 44
nc 288
nop 1
crap 210

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 SqlServer Adapter.
38
 *
39
 * @author Rob Morgan <[email protected]>
40
 */
41
class SqlServerAdapter extends PdoAdapter implements AdapterInterface
42
{
43
    protected $schema = 'dbo';
44
45
    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true];
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function connect()
51
    {
52
        if ($this->connection === null) {
53
            if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
54
                // try our connection via freetds (Mac/Linux)
55
                $this->connectDblib();
56
57
                return;
58
            }
59
60
            $db = null;
61
            $options = $this->getOptions();
62
63
            // if port is specified use it, otherwise use the SqlServer default
64 View Code Duplication
            if (empty($options['port'])) {
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...
65
                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
66
            } else {
67
                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
68
            }
69
            $dsn .= ';MultipleActiveResultSets=false';
70
71
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
72
73
            // charset support
74
            if (isset($options['charset'])) {
75
                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
76
            }
77
78
            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
79
            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
80 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...
81
                if (strpos($key, 'sqlsrv_attr_') === 0) {
82
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
83
                }
84
            }
85
86
            try {
87
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
88
            } catch (\PDOException $exception) {
89
                throw new \InvalidArgumentException(sprintf(
90
                    'There was a problem connecting to the database: %s',
91
                    $exception->getMessage()
92
                ));
93
            }
94
95
            $this->setConnection($db);
96
        }
97
    }
98
99
    /**
100
     * Connect to MSSQL using dblib/freetds.
101
     *
102
     * The "sqlsrv" driver is not available on Unix machines.
103
     *
104
     * @throws \InvalidArgumentException
105
     */
106
    protected function connectDblib()
107
    {
108
        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
109
            // @codeCoverageIgnoreStart
110
            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
111
            // @codeCoverageIgnoreEnd
112
        }
113
114
        $options = $this->getOptions();
115
116
        // if port is specified use it, otherwise use the SqlServer default
117 View Code Duplication
        if (empty($options['port'])) {
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...
118
            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
119
        } else {
120
            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
121
        }
122
123
        $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
124
125
        try {
126
            $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
127
        } catch (\PDOException $exception) {
128
            throw new \InvalidArgumentException(sprintf(
129
                'There was a problem connecting to the database: %s',
130
                $exception->getMessage()
131
            ));
132
        }
133
134
        $this->setConnection($db);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function disconnect()
141
    {
142
        $this->connection = null;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function hasTransactions()
149
    {
150
        return true;
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function beginTransaction()
157
    {
158
        $this->execute('BEGIN TRANSACTION');
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function commitTransaction()
165
    {
166
        $this->execute('COMMIT TRANSACTION');
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function rollbackTransaction()
173
    {
174
        $this->execute('ROLLBACK TRANSACTION');
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function quoteTableName($tableName)
181
    {
182
        return str_replace('.', '].[', $this->quoteColumnName($tableName));
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function quoteColumnName($columnName)
189
    {
190
        return '[' . str_replace(']', '\]', $columnName) . ']';
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196
    public function hasTable($tableName)
197
    {
198
        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
199
200
        return $result['count'] > 0;
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function createTable(Table $table)
207
    {
208
        $options = $table->getOptions();
209
210
        // Add the default primary key
211
        $columns = $table->getPendingColumns();
212 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
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...
213
            $column = new Column();
214
            $column->setName('id')
215
                   ->setType('integer')
216
                   ->setIdentity(true);
217
218
            array_unshift($columns, $column);
219
            $options['primary_key'] = 'id';
220
        } elseif (isset($options['id']) && is_string($options['id'])) {
221
            // Handle id => "field_name" to support AUTO_INCREMENT
222
            $column = new Column();
223
            $column->setName($options['id'])
224
                   ->setType('integer')
225
                   ->setIdentity(true);
226
227
            array_unshift($columns, $column);
228
            $options['primary_key'] = $options['id'];
229
        }
230
231
        $sql = 'CREATE TABLE ';
232
        $sql .= $this->quoteTableName($table->getName()) . ' (';
233
        $sqlBuffer = [];
234
        $columnsWithComments = [];
235 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...
236
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
237
238
            // set column comments, if needed
239
            if ($column->getComment()) {
240
                $columnsWithComments[] = $column;
241
            }
242
        }
243
244
        // set the primary key(s)
245
        if (isset($options['primary_key'])) {
246
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
247 View Code Duplication
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
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...
248
                $pkSql .= $this->quoteColumnName($options['primary_key']);
249
            } 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...
250
                $pkSql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
251
            }
252
            $pkSql .= ')';
253
            $sqlBuffer[] = $pkSql;
254
        }
255
256
        // set the foreign keys
257
        $foreignKeys = $table->getForeignKeys();
258
        foreach ($foreignKeys as $foreignKey) {
259
            $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
260
        }
261
262
        $sql .= implode(', ', $sqlBuffer);
263
        $sql .= ');';
264
265
        // process column comments
266
        foreach ($columnsWithComments as $column) {
267
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
268
        }
269
270
        // set the indexes
271
        $indexes = $table->getIndexes();
272
        foreach ($indexes as $index) {
273
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
274
        }
275
276
        // execute the sql
277
        $this->execute($sql);
278
    }
279
280
    /**
281
     * Gets the SqlServer Column Comment Defininition for a column object.
282
     *
283
     * @param \Phinx\Db\Table\Column $column    Column
284
     * @param string $tableName Table name
285
     *
286
     * @return string
287
     */
288
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
289
    {
290
        // passing 'null' is to remove column comment
291
        $currentComment = $this->getColumnComment($tableName, $column->getName());
292
293
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
294
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
295
296
        return sprintf(
297
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
298
            $command,
299
            $comment,
300
            $this->schema,
301
            $tableName,
302
            $column->getName()
303
        );
304
    }
305
306
    /**
307
     * {@inheritdoc}
308
     */
309
    public function renameTable($tableName, $newTableName)
310
    {
311
        $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function dropTable($tableName)
318
    {
319
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325
    public function truncateTable($tableName)
326
    {
327
        $sql = sprintf(
328
            'TRUNCATE TABLE %s',
329
            $this->quoteTableName($tableName)
330
        );
331
332
        $this->execute($sql);
333
    }
334
335
    public function getColumnComment($tableName, $columnName)
336
    {
337
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
338
  FROM sys.schemas
339
 INNER JOIN sys.tables
340
    ON schemas.schema_id = tables.schema_id
341
 INNER JOIN sys.columns
342
    ON tables.object_id = columns.object_id
343
 INNER JOIN sys.extended_properties
344
    ON tables.object_id = extended_properties.major_id
345
   AND columns.column_id = extended_properties.minor_id
346
   AND extended_properties.name = 'MS_Description'
347
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
348
        $row = $this->fetchRow($sql);
349
350
        if ($row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
351
            return $row['comment'];
352
        }
353
354
        return false;
355
    }
356
357
    /**
358
     * {@inheritdoc}
359
     */
360
    public function getColumns($tableName)
361
    {
362
        $columns = [];
363
        $sql = sprintf(
364
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
365
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
366
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
367
            NUMERIC_PRECISION AS [precision],
368
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
369
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
370
        FROM INFORMATION_SCHEMA.COLUMNS
371
        WHERE TABLE_NAME = '%s'
372
        ORDER BY ordinal_position",
373
            $tableName
374
        );
375
        $rows = $this->fetchAll($sql);
376
        foreach ($rows as $columnInfo) {
377
            $column = new Column();
378
            $column->setName($columnInfo['name'])
379
                   ->setType($this->getPhinxType($columnInfo['type']))
380
                   ->setNull($columnInfo['null'] !== 'NO')
381
                   ->setDefault($this->parseDefault($columnInfo['default']))
382
                   ->setIdentity($columnInfo['identity'] === '1')
383
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
384
385
            if (!empty($columnInfo['char_length'])) {
386
                $column->setLimit($columnInfo['char_length']);
387
            }
388
389
            $columns[$columnInfo['name']] = $column;
390
        }
391
392
        return $columns;
393
    }
394
395
    protected function parseDefault($default)
396
    {
397
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
398
399
        if (strtoupper($default) === 'NULL') {
400
            $default = null;
401
        } elseif (is_numeric($default)) {
402
            $default = (int)$default;
403
        }
404
405
        return $default;
406
    }
407
408
    /**
409
     * {@inheritdoc}
410
     */
411 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...
412
    {
413
        $sql = sprintf(
414
            "SELECT count(*) as [count]
415
             FROM information_schema.columns
416
             WHERE table_name = '%s' AND column_name = '%s'",
417
            $tableName,
418
            $columnName
419
        );
420
        $result = $this->fetchRow($sql);
421
422
        return $result['count'] > 0;
423
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428 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...
429
    {
430
        $sql = sprintf(
431
            'ALTER TABLE %s ADD %s %s',
432
            $this->quoteTableName($table->getName()),
433
            $this->quoteColumnName($column->getName()),
434
            $this->getColumnSqlDefinition($column)
435
        );
436
437
        $this->execute($sql);
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function renameColumn($tableName, $columnName, $newColumnName)
444
    {
445
        if (!$this->hasColumn($tableName, $columnName)) {
446
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
447
        }
448
        $this->renameDefault($tableName, $columnName, $newColumnName);
449
        $this->execute(
450
            sprintf(
451
                "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
452
                $tableName,
453
                $columnName,
454
                $newColumnName
455
            )
456
        );
457
    }
458
459
    protected function renameDefault($tableName, $columnName, $newColumnName)
460
    {
461
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
462
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
463
        $sql = <<<SQL
464
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
465
BEGIN
466
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
467
END
468
SQL;
469
        $this->execute(sprintf(
470
            $sql,
471
            $oldConstraintName,
472
            $newConstraintName
473
        ));
474
    }
475
476
    public function changeDefault($tableName, Column $newColumn)
477
    {
478
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
479
        $default = $newColumn->getDefault();
480
481
        if ($default === null) {
482
            $default = 'DEFAULT NULL';
483
        } else {
484
            $default = $this->getDefaultValueDefinition($default);
485
        }
486
487
        if (empty($default)) {
488
            return;
489
        }
490
491
        $this->execute(sprintf(
492
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
493
            $this->quoteTableName($tableName),
494
            $constraintName,
495
            $default,
496
            $this->quoteColumnName($newColumn->getName())
497
        ));
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503
    public function changeColumn($tableName, $columnName, Column $newColumn)
504
    {
505
        $columns = $this->getColumns($tableName);
506
        $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
507
        if ($columnName !== $newColumn->getName()) {
508
            $this->renameColumn($tableName, $columnName, $newColumn->getName());
509
        }
510
511
        if ($changeDefault) {
512
            $this->dropDefaultConstraint($tableName, $newColumn->getName());
513
        }
514
515
        $this->execute(
516
            sprintf(
517
                'ALTER TABLE %s ALTER COLUMN %s %s',
518
                $this->quoteTableName($tableName),
519
                $this->quoteColumnName($newColumn->getName()),
520
                $this->getColumnSqlDefinition($newColumn, false)
521
            )
522
        );
523
        // change column comment if needed
524
        if ($newColumn->getComment()) {
525
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
526
            $this->execute($sql);
527
        }
528
529
        if ($changeDefault) {
530
            $this->changeDefault($tableName, $newColumn);
531
        }
532
    }
533
534
    /**
535
     * {@inheritdoc}
536
     */
537
    public function dropColumn($tableName, $columnName)
538
    {
539
        $this->dropDefaultConstraint($tableName, $columnName);
540
541
        $this->execute(
542
            sprintf(
543
                'ALTER TABLE %s DROP COLUMN %s',
544
                $this->quoteTableName($tableName),
545
                $this->quoteColumnName($columnName)
546
            )
547
        );
548
    }
549
550
    protected function dropDefaultConstraint($tableName, $columnName)
551
    {
552
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
553
554
        if (!$defaultConstraint) {
555
            return;
556
        }
557
558
        $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
559
    }
560
561
    protected function getDefaultConstraint($tableName, $columnName)
562
    {
563
        $sql = "SELECT
564
    default_constraints.name
565
FROM
566
    sys.all_columns
567
568
        INNER JOIN
569
    sys.tables
570
        ON all_columns.object_id = tables.object_id
571
572
        INNER JOIN
573
    sys.schemas
574
        ON tables.schema_id = schemas.schema_id
575
576
        INNER JOIN
577
    sys.default_constraints
578
        ON all_columns.default_object_id = default_constraints.object_id
579
580
WHERE
581
        schemas.name = 'dbo'
582
    AND tables.name = '{$tableName}'
583
    AND all_columns.name = '{$columnName}'";
584
585
        $rows = $this->fetchAll($sql);
586
587
        return empty($rows) ? false : $rows[0]['name'];
588
    }
589
590
    protected function getIndexColums($tableId, $indexId)
591
    {
592
        $sql = "SELECT AC.[name] AS [column_name]
593
FROM sys.[index_columns] IC
594
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
595
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
596
ORDER BY IC.[key_ordinal];";
597
598
        $rows = $this->fetchAll($sql);
599
        $columns = [];
600
        foreach ($rows as $row) {
601
            $columns[] = strtolower($row['column_name']);
602
        }
603
604
        return $columns;
605
    }
606
607
    /**
608
     * Get an array of indexes from a particular table.
609
     *
610
     * @param string $tableName Table Name
611
     * @return array
612
     */
613
    public function getIndexes($tableName)
614
    {
615
        $indexes = [];
616
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
617
FROM sys.[tables] AS T
618
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
619
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
620
ORDER BY T.[name], I.[index_id];";
621
622
        $rows = $this->fetchAll($sql);
623
        foreach ($rows as $row) {
624
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
625
            $indexes[$row['index_name']] = ['columns' => $columns];
626
        }
627
628
        return $indexes;
629
    }
630
631
    /**
632
     * {@inheritdoc}
633
     */
634 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...
635
    {
636
        if (is_string($columns)) {
637
            $columns = [$columns]; // str to array
638
        }
639
640
        $columns = array_map('strtolower', $columns);
641
        $indexes = $this->getIndexes($tableName);
642
643
        foreach ($indexes as $index) {
644
            $a = array_diff($columns, $index['columns']);
645
646
            if (empty($a)) {
647
                return true;
648
            }
649
        }
650
651
        return false;
652
    }
653
654
    /**
655
     * {@inheritdoc}
656
     */
657 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...
658
    {
659
        $indexes = $this->getIndexes($tableName);
660
661
        foreach ($indexes as $name => $index) {
662
            if ($name === $indexName) {
663
                 return true;
664
            }
665
        }
666
667
        return false;
668
    }
669
670
    /**
671
     * {@inheritdoc}
672
     */
673
    public function addIndex(Table $table, Index $index)
674
    {
675
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
676
        $this->execute($sql);
677
    }
678
679
    /**
680
     * {@inheritdoc}
681
     */
682 View Code Duplication
    public function dropIndex($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...
683
    {
684
        if (is_string($columns)) {
685
            $columns = [$columns]; // str to array
686
        }
687
688
        $indexes = $this->getIndexes($tableName);
689
        $columns = array_map('strtolower', $columns);
690
691
        foreach ($indexes as $indexName => $index) {
692
            $a = array_diff($columns, $index['columns']);
693
            if (empty($a)) {
694
                $this->execute(
695
                    sprintf(
696
                        'DROP INDEX %s ON %s',
697
                        $this->quoteColumnName($indexName),
698
                        $this->quoteTableName($tableName)
699
                    )
700
                );
701
702
                return;
703
            }
704
        }
705
    }
706
707
    /**
708
     * {@inheritdoc}
709
     */
710 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...
711
    {
712
        $indexes = $this->getIndexes($tableName);
713
714
        foreach ($indexes as $name => $index) {
715
            if ($name === $indexName) {
716
                $this->execute(
717
                    sprintf(
718
                        'DROP INDEX %s ON %s',
719
                        $this->quoteColumnName($indexName),
720
                        $this->quoteTableName($tableName)
721
                    )
722
                );
723
724
                return;
725
            }
726
        }
727
    }
728
729
    /**
730
     * {@inheritdoc}
731
     */
732 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...
733
    {
734
        if (is_string($columns)) {
735
            $columns = [$columns]; // str to array
736
        }
737
        $foreignKeys = $this->getForeignKeys($tableName);
738
        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...
739
            if (isset($foreignKeys[$constraint])) {
740
                return !empty($foreignKeys[$constraint]);
741
            }
742
743
            return false;
744
        } else {
745
            foreach ($foreignKeys as $key) {
746
                $a = array_diff($columns, $key['columns']);
747
                if (empty($a)) {
748
                    return true;
749
                }
750
            }
751
752
            return false;
753
        }
754
    }
755
756
    /**
757
     * Get an array of foreign keys from a particular table.
758
     *
759
     * @param string $tableName Table Name
760
     * @return array
761
     */
762 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...
763
    {
764
        $foreignKeys = [];
765
        $rows = $this->fetchAll(sprintf(
766
            "SELECT
767
                    tc.constraint_name,
768
                    tc.table_name, kcu.column_name,
769
                    ccu.table_name AS referenced_table_name,
770
                    ccu.column_name AS referenced_column_name
771
                FROM
772
                    information_schema.table_constraints AS tc
773
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
774
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
775
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
776
                ORDER BY kcu.ordinal_position",
777
            $tableName
778
        ));
779
        foreach ($rows as $row) {
780
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
781
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
782
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
783
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
784
        }
785
786
        return $foreignKeys;
787
    }
788
789
    /**
790
     * {@inheritdoc}
791
     */
792 View Code Duplication
    public function addForeignKey(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...
793
    {
794
        $this->execute(
795
            sprintf(
796
                'ALTER TABLE %s ADD %s',
797
                $this->quoteTableName($table->getName()),
798
                $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
799
            )
800
        );
801
    }
802
803
    /**
804
     * {@inheritdoc}
805
     */
806 View Code Duplication
    public function dropForeignKey($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...
807
    {
808
        if (is_string($columns)) {
809
            $columns = [$columns]; // str to array
810
        }
811
812
        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...
813
            $this->execute(
814
                sprintf(
815
                    'ALTER TABLE %s DROP CONSTRAINT %s',
816
                    $this->quoteTableName($tableName),
817
                    $constraint
818
                )
819
            );
820
821
            return;
822
        } else {
823
            foreach ($columns as $column) {
824
                $rows = $this->fetchAll(sprintf(
825
                    "SELECT
826
                    tc.constraint_name,
827
                    tc.table_name, kcu.column_name,
828
                    ccu.table_name AS referenced_table_name,
829
                    ccu.column_name AS referenced_column_name
830
                FROM
831
                    information_schema.table_constraints AS tc
832
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
833
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
834
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
835
                ORDER BY kcu.ordinal_position",
836
                    $tableName,
837
                    $column
838
                ));
839
                foreach ($rows as $row) {
840
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
841
                }
842
            }
843
        }
844
    }
845
846
    /**
847
     * {@inheritdoc}
848
     */
849
    public function getSqlType($type, $limit = null)
850
    {
851
        switch ($type) {
852
            case static::PHINX_TYPE_STRING:
853
                return ['name' => 'nvarchar', 'limit' => 255];
854
            case static::PHINX_TYPE_CHAR:
855
                return ['name' => 'nchar', 'limit' => 255];
856
            case static::PHINX_TYPE_TEXT:
857
                return ['name' => 'ntext'];
858
            case static::PHINX_TYPE_INTEGER:
859
                return ['name' => 'int'];
860
            case static::PHINX_TYPE_BIG_INTEGER:
861
                return ['name' => 'bigint'];
862
            case static::PHINX_TYPE_FLOAT:
863
                return ['name' => 'float'];
864
            case static::PHINX_TYPE_DECIMAL:
865
                return ['name' => 'decimal'];
866
            case static::PHINX_TYPE_DATETIME:
867
            case static::PHINX_TYPE_TIMESTAMP:
868
                return ['name' => 'datetime'];
869
            case static::PHINX_TYPE_TIME:
870
                return ['name' => 'time'];
871
            case static::PHINX_TYPE_DATE:
872
                return ['name' => 'date'];
873
            case static::PHINX_TYPE_BLOB:
874
            case static::PHINX_TYPE_BINARY:
875
                return ['name' => 'varbinary'];
876
            case static::PHINX_TYPE_BOOLEAN:
877
                return ['name' => 'bit'];
878
            case static::PHINX_TYPE_UUID:
879
                return ['name' => 'uniqueidentifier'];
880
            case static::PHINX_TYPE_FILESTREAM:
881
                return ['name' => 'varbinary', 'limit' => 'max'];
882
            // Geospatial database types
883
            case static::PHINX_TYPE_GEOMETRY:
884
            case static::PHINX_TYPE_POINT:
885
            case static::PHINX_TYPE_LINESTRING:
886
            case static::PHINX_TYPE_POLYGON:
887
                // SQL Server stores all spatial data using a single data type.
888
                // Specific types (point, polygon, etc) are set at insert time.
889
                return ['name' => 'geography'];
890
            default:
891
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
892
        }
893
    }
894
895
    /**
896
     * Returns Phinx type by SQL type
897
     *
898
     * @param string $sqlType SQL Type definition
899
     * @throws \RuntimeException
900
     * @internal param string $sqlType SQL type
901
     * @returns string Phinx type
902
     */
903
    public function getPhinxType($sqlType)
904
    {
905
        switch ($sqlType) {
906
            case 'nvarchar':
907
            case 'varchar':
908
                return static::PHINX_TYPE_STRING;
909
            case 'char':
910
            case 'nchar':
911
                return static::PHINX_TYPE_CHAR;
912
            case 'text':
913
            case 'ntext':
914
                return static::PHINX_TYPE_TEXT;
915
            case 'int':
916
            case 'integer':
917
                return static::PHINX_TYPE_INTEGER;
918
            case 'decimal':
919
            case 'numeric':
920
            case 'money':
921
                return static::PHINX_TYPE_DECIMAL;
922
            case 'bigint':
923
                return static::PHINX_TYPE_BIG_INTEGER;
924
            case 'real':
925
            case 'float':
926
                return static::PHINX_TYPE_FLOAT;
927
            case 'binary':
928
            case 'image':
929
            case 'varbinary':
930
                return static::PHINX_TYPE_BINARY;
931
            case 'time':
932
                return static::PHINX_TYPE_TIME;
933
            case 'date':
934
                return static::PHINX_TYPE_DATE;
935
            case 'datetime':
936
            case 'timestamp':
937
                return static::PHINX_TYPE_DATETIME;
938
            case 'bit':
939
                return static::PHINX_TYPE_BOOLEAN;
940
            case 'uniqueidentifier':
941
                return static::PHINX_TYPE_UUID;
942
            case 'filestream':
943
                return static::PHINX_TYPE_FILESTREAM;
944
            default:
945
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
946
        }
947
    }
948
949
    /**
950
     * {@inheritdoc}
951
     */
952
    public function createDatabase($name, $options = [])
953
    {
954 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...
955
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
956
        } else {
957
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
958
        }
959
        $this->execute(sprintf('USE [%s]', $name));
960
    }
961
962
    /**
963
     * {@inheritdoc}
964
     */
965
    public function hasDatabase($name)
966
    {
967
        $result = $this->fetchRow(
968
            sprintf(
969
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
970
                $name
971
            )
972
        );
973
974
        return $result['count'] > 0;
975
    }
976
977
    /**
978
     * {@inheritdoc}
979
     */
980
    public function dropDatabase($name)
981
    {
982
        $sql = <<<SQL
983
USE master;
984
IF EXISTS(select * from sys.databases where name=N'$name')
985
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
986
DROP DATABASE [$name];
987
SQL;
988
        $this->execute($sql);
989
    }
990
991
    /**
992
     * Get the defintion for a `DEFAULT` statement.
993
     *
994
     * @param  mixed $default
995
     * @return string
996
     */
997 View Code Duplication
    protected function getDefaultValueDefinition($default)
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...
998
    {
999
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
1000
            $default = $this->getConnection()->quote($default);
1001
        } elseif (is_bool($default)) {
1002
            $default = $this->castToBool($default);
1003
        }
1004
1005
        return isset($default) ? ' DEFAULT ' . $default : '';
1006
    }
1007
1008
    /**
1009
     * Gets the SqlServer Column Definition for a Column object.
1010
     *
1011
     * @param \Phinx\Db\Table\Column $column Column
1012
     * @return string
1013
     */
1014
    protected function getColumnSqlDefinition(Column $column, $create = true)
1015
    {
1016
        $buffer = [];
1017
1018
        $sqlType = $this->getSqlType($column->getType());
1019
        $buffer[] = strtoupper($sqlType['name']);
1020
        // integers cant have limits in SQlServer
1021
        $noLimits = [
1022
            'bigint',
1023
            'int',
1024
            'tinyint'
1025
        ];
1026
        if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1027
            $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
1028
        }
1029 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...
1030
            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1031
        }
1032
1033
        $properties = $column->getProperties();
1034
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
1035
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
1036
1037
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1038
1039
        if ($create === true) {
1040
            if ($column->getDefault() === null && $column->isNull()) {
1041
                $buffer[] = ' DEFAULT NULL';
1042
            } else {
1043
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1044
            }
1045
        }
1046
1047
        if ($column->isIdentity()) {
1048
            $buffer[] = 'IDENTITY(1, 1)';
1049
        }
1050
1051
        return implode(' ', $buffer);
1052
    }
1053
1054
    /**
1055
     * Gets the SqlServer Index Definition for an Index object.
1056
     *
1057
     * @param \Phinx\Db\Table\Index $index Index
1058
     * @return string
1059
     */
1060
    protected function getIndexSqlDefinition(Index $index, $tableName)
1061
    {
1062 View Code Duplication
        if (is_string($index->getName())) {
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...
1063
            $indexName = $index->getName();
1064
        } else {
1065
            $columnNames = $index->getColumns();
1066
            if (is_string($columnNames)) {
1067
                $columnNames = [$columnNames];
1068
            }
1069
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1070
        }
1071
        $def = sprintf(
1072
            "CREATE %s INDEX %s ON %s (%s);",
1073
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1074
            $indexName,
1075
            $this->quoteTableName($tableName),
1076
            '[' . implode('],[', $index->getColumns()) . ']'
1077
        );
1078
1079
        return $def;
1080
    }
1081
1082
    /**
1083
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1084
     *
1085
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1086
     * @return string
1087
     */
1088 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $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...
1089
    {
1090
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1091
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1090 can also be of type boolean; however, Phinx\Db\Adapter\SqlServ...pter::quoteColumnName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1092
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1093
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1094
        if ($foreignKey->getOnDelete()) {
1095
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1096
        }
1097
        if ($foreignKey->getOnUpdate()) {
1098
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1099
        }
1100
1101
        return $def;
1102
    }
1103
1104
    /**
1105
     * {@inheritdoc}
1106
     */
1107
    public function getColumnTypes()
1108
    {
1109
        return array_merge(parent::getColumnTypes(), ['filestream']);
1110
    }
1111
1112
    /**
1113
     * Records a migration being run.
1114
     *
1115
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1116
     * @param string $direction Direction
1117
     * @param int $startTime Start Time
1118
     * @param int $endTime End Time
1119
     * @return \Phinx\Db\Adapter\AdapterInterface
1120
     */
1121
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1122
    {
1123
        $startTime = str_replace(' ', 'T', $startTime);
1124
        $endTime = str_replace(' ', 'T', $endTime);
1125
1126
        return parent::migrated($migration, $direction, $startTime, $endTime);
1127
    }
1128
}
1129