Failed Conditions
Pull Request — master (#2850)
by Adrien
26:35
created

_getPortableTableColumnDefinition()   F

Complexity

Conditions 15
Paths 1088

Size

Total Lines 69
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 15.0173

Importance

Changes 0
Metric Value
dl 0
loc 69
ccs 45
cts 47
cp 0.9574
rs 2.5316
c 0
b 0
f 0
cc 15
eloc 49
nc 1088
nop 1
crap 15.0173

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Schema;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Types\StringType;
24
use Doctrine\DBAL\Types\TextType;
25
use Doctrine\DBAL\Types\Type;
26
27
/**
28
 * Sqlite SchemaManager.
29
 *
30
 * @author Konsta Vesterinen <[email protected]>
31
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
32
 * @author Jonathan H. Wage <[email protected]>
33
 * @author Martin Hasoň <[email protected]>
34
 * @since  2.0
35
 */
36
class SqliteSchemaManager extends AbstractSchemaManager
37
{
38
    /**
39
     * {@inheritdoc}
40
     */
41 2
    public function dropDatabase($database)
42
    {
43 2
        if (file_exists($database)) {
44 2
            unlink($database);
45
        }
46 2
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 2
    public function createDatabase($database)
52
    {
53 2
        $params = $this->_conn->getParams();
54 2
        $driver = $params['driver'];
55
        $options = [
56 2
            'driver' => $driver,
57 2
            'path' => $database
58
        ];
59 2
        $conn = \Doctrine\DBAL\DriverManager::getConnection($options);
60 2
        $conn->connect();
61 2
        $conn->close();
62 2
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 1
    public function renameTable($name, $newName)
68
    {
69 1
        $tableDiff = new TableDiff($name);
70 1
        $tableDiff->fromTable = $this->listTableDetails($name);
71 1
        $tableDiff->newName = $newName;
72 1
        $this->alterTable($tableDiff);
73 1
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
79
    {
80
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
81
        $tableDiff->addedForeignKeys[] = $foreignKey;
82
83
        $this->alterTable($tableDiff);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
90
    {
91
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
92
        $tableDiff->changedForeignKeys[] = $foreignKey;
93
94
        $this->alterTable($tableDiff);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function dropForeignKey($foreignKey, $table)
101
    {
102
        $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
103
        $tableDiff->removedForeignKeys[] = $foreignKey;
104
105
        $this->alterTable($tableDiff);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 1
    public function listTableForeignKeys($table, $database = null)
112
    {
113 1
        if (null === $database) {
114 1
            $database = $this->_conn->getDatabase();
115
        }
116 1
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Platforms\...stTableForeignKeysSQL() has too many arguments starting with $database. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
        /** @scrutinizer ignore-call */ 
117
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
117 1
        $tableForeignKeys = $this->_conn->fetchAll($sql);
118
119 1
        if ( ! empty($tableForeignKeys)) {
120 1
            $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
121 1
            $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : '';
122
123 1
            if (preg_match_all('#
124
                    (?:CONSTRAINT\s+([^\s]+)\s+)?
125
                    (?:FOREIGN\s+KEY[^\)]+\)\s*)?
126
                    REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))?
127
                    (?:
128
                        [^,]*?
129
                        (NOT\s+DEFERRABLE|DEFERRABLE)
130
                        (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
131
                    )?#isx',
132 1
                    $createSql, $match)) {
133
134 1
                $names = array_reverse($match[1]);
135 1
                $deferrable = array_reverse($match[2]);
136 1
                $deferred = array_reverse($match[3]);
137
            } else {
138
                $names = $deferrable = $deferred = [];
139
            }
140
141 1
            foreach ($tableForeignKeys as $key => $value) {
142 1
                $id = $value['id'];
143 1
                $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id;
144 1
                $tableForeignKeys[$key]['deferrable'] = isset($deferrable[$id]) && 'deferrable' == strtolower($deferrable[$id]) ? true : false;
145 1
                $tableForeignKeys[$key]['deferred'] = isset($deferred[$id]) && 'deferred' == strtolower($deferred[$id]) ? true : false;
146
            }
147
        }
148
149 1
        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 76
    protected function _getPortableTableDefinition($table)
156
    {
157 76
        return $table['name'];
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     *
163
     * @license New BSD License
164
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
165
     */
166 46
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
167
    {
168 46
        $indexBuffer = [];
169
170
        // fetch primary
171 46
        $stmt = $this->_conn->executeQuery("PRAGMA TABLE_INFO ('$tableName')");
172 46
        $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
173
174 46
        usort($indexArray, function($a, $b) {
175 35
            if ($a['pk'] == $b['pk']) {
176 31
                return $a['cid'] - $b['cid'];
177
            }
178
179 12
            return $a['pk'] - $b['pk'];
180 46
        });
181 46
        foreach ($indexArray as $indexColumnRow) {
182 46
            if ($indexColumnRow['pk'] != "0") {
183 21
                $indexBuffer[] = [
184 21
                    'key_name' => 'primary',
185
                    'primary' => true,
186
                    'non_unique' => false,
187 46
                    'column_name' => $indexColumnRow['name']
188
                ];
189
            }
190
        }
191
192
        // fetch regular indexes
193 46
        foreach ($tableIndexes as $tableIndex) {
194
            // Ignore indexes with reserved names, e.g. autoindexes
195 8
            if (strpos($tableIndex['name'], 'sqlite_') !== 0) {
196 6
                $keyName = $tableIndex['name'];
197 6
                $idx = [];
198 6
                $idx['key_name'] = $keyName;
199 6
                $idx['primary'] = false;
200 6
                $idx['non_unique'] = $tableIndex['unique']?false:true;
201
202 6
                $stmt = $this->_conn->executeQuery("PRAGMA INDEX_INFO ('{$keyName}')");
203 6
                $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
204
205 6
                foreach ($indexArray as $indexColumnRow) {
206 6
                    $idx['column_name'] = $indexColumnRow['name'];
207 8
                    $indexBuffer[] = $idx;
208
                }
209
            }
210
        }
211
212 46
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    protected function _getPortableTableIndexDefinition($tableIndex)
219
    {
220
        return [
221
            'name' => $tableIndex['name'],
222
            'unique' => (bool) $tableIndex['unique']
223
        ];
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 53
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
230
    {
231 53
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
232
233
        // find column with autoincrement
234 53
        $autoincrementColumn = null;
235 53
        $autoincrementCount = 0;
236
237 53
        foreach ($tableColumns as $tableColumn) {
238 53
            if ('0' != $tableColumn['pk']) {
239 24
                $autoincrementCount++;
240 24
                if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) {
241 53
                    $autoincrementColumn = $tableColumn['name'];
242
                }
243
            }
244
        }
245
246 53
        if (1 == $autoincrementCount && null !== $autoincrementColumn) {
247 23
            foreach ($list as $column) {
248 23
                if ($autoincrementColumn == $column->getName()) {
249 23
                    $column->setAutoincrement(true);
250
                }
251
            }
252
        }
253
254
        // inspect column collation and comments
255 53
        $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
256 53
        $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : '';
257
258 53
        foreach ($list as $columnName => $column) {
259 53
            $type = $column->getType();
260
261 53
            if ($type instanceof StringType || $type instanceof TextType) {
262 29
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
263
            }
264
265 53
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
266
267 53
            if ($comment !== null) {
268 16
                $type = $this->extractDoctrineTypeFromComment($comment, null);
269
270 16
                if (null !== $type) {
271 4
                    $column->setType(Type::getType($type));
272
273 4
                    $comment = $this->removeDoctrineTypeFromComment($comment, $type);
274
                }
275
276 53
                $column->setComment($comment);
277
            }
278
        }
279
280 53
        return $list;
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286 53
    protected function _getPortableTableColumnDefinition($tableColumn)
287
    {
288 53
        $parts = explode('(', $tableColumn['type']);
289 53
        $tableColumn['type'] = trim($parts[0]);
290 53
        if (isset($parts[1])) {
291 25
            $length = trim($parts[1], ')');
292 25
            $tableColumn['length'] = $length;
293
        }
294
295 53
        $dbType = strtolower($tableColumn['type']);
296 53
        $length = isset($tableColumn['length']) ? $tableColumn['length'] : null;
297 53
        $unsigned = false;
298
299 53
        if (strpos($dbType, ' unsigned') !== false) {
300 1
            $dbType = str_replace(' unsigned', '', $dbType);
301 1
            $unsigned = true;
302
        }
303
304 53
        $fixed = false;
305 53
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
306 53
        $default = $tableColumn['dflt_value'];
307 53
        if ($default == 'NULL') {
308 4
            $default = null;
309
        }
310 53
        if ($default !== null) {
311
            // SQLite returns strings wrapped in single quotes and escaped, so we need to strip them
312 19
            $default = preg_replace("/^'(.*)'$/s", '\1', $default);
313 19
            $default = str_replace("''", "'", $default);
314
        }
315 53
        $notnull = (bool) $tableColumn['notnull'];
316
317 53
        if ( ! isset($tableColumn['name'])) {
318
            $tableColumn['name'] = '';
319
        }
320
321 53
        $precision = null;
322 53
        $scale = null;
323
324
        switch ($dbType) {
325 53
            case 'char':
326 4
                $fixed = true;
327 4
                break;
328 52
            case 'float':
329 52
            case 'double':
330 52
            case 'real':
331 52
            case 'decimal':
332 52
            case 'numeric':
333 4
                if (isset($tableColumn['length'])) {
334 4
                    if (strpos($tableColumn['length'], ',') === false) {
335
                        $tableColumn['length'] .= ",0";
336
                    }
337 4
                    list($precision, $scale) = array_map('trim', explode(',', $tableColumn['length']));
338
                }
339 4
                $length = null;
340 4
                break;
341
        }
342
343
        $options = [
344 53
            'length'   => $length,
345 53
            'unsigned' => (bool) $unsigned,
346 53
            'fixed'    => $fixed,
347 53
            'notnull'  => $notnull,
348 53
            'default'  => $default,
349 53
            'precision' => $precision,
350 53
            'scale'     => $scale,
351
            'autoincrement' => false,
352
        ];
353
354 53
        return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options);
355
    }
356
357
    /**
358
     * {@inheritdoc}
359
     */
360 1
    protected function _getPortableViewDefinition($view)
361
    {
362 1
        return new View($view['name'], $view['sql']);
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368 1
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
369
    {
370 1
        $list = [];
371 1
        foreach ($tableForeignKeys as $value) {
372 1
            $value = array_change_key_case($value, CASE_LOWER);
373 1
            $name = $value['constraint_name'];
374 1
            if ( ! isset($list[$name])) {
375 1
                if ( ! isset($value['on_delete']) || $value['on_delete'] == "RESTRICT") {
376
                    $value['on_delete'] = null;
377
                }
378 1
                if ( ! isset($value['on_update']) || $value['on_update'] == "RESTRICT") {
379
                    $value['on_update'] = null;
380
                }
381
382 1
                $list[$name] = [
383 1
                    'name' => $name,
384
                    'local' => [],
385
                    'foreign' => [],
386 1
                    'foreignTable' => $value['table'],
387 1
                    'onDelete' => $value['on_delete'],
388 1
                    'onUpdate' => $value['on_update'],
389 1
                    'deferrable' => $value['deferrable'],
390 1
                    'deferred'=> $value['deferred'],
391
                ];
392
            }
393 1
            $list[$name]['local'][] = $value['from'];
394 1
            $list[$name]['foreign'][] = $value['to'];
395
        }
396
397 1
        $result = [];
398 1
        foreach ($list as $constraint) {
399 1
            $result[] = new ForeignKeyConstraint(
400 1
                array_values($constraint['local']), $constraint['foreignTable'],
401 1
                array_values($constraint['foreign']), $constraint['name'],
402
                [
403 1
                    'onDelete' => $constraint['onDelete'],
404 1
                    'onUpdate' => $constraint['onUpdate'],
405 1
                    'deferrable' => $constraint['deferrable'],
406 1
                    'deferred'=> $constraint['deferred'],
407
                ]
408
            );
409
        }
410
411 1
        return $result;
412
    }
413
414
    /**
415
     * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
416
     * @param \Doctrine\DBAL\Schema\Table|string         $table
417
     *
418
     * @return \Doctrine\DBAL\Schema\TableDiff
419
     *
420
     * @throws \Doctrine\DBAL\DBALException
421
     */
422
    private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table)
0 ignored issues
show
Unused Code introduced by
The parameter $foreignKey is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

422
    private function getTableDiffForAlterForeignKey(/** @scrutinizer ignore-unused */ ForeignKeyConstraint $foreignKey, $table)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
423
    {
424
        if ( ! $table instanceof Table) {
425
            $tableDetails = $this->tryMethod('listTableDetails', $table);
426
            if (false === $table) {
427
                throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
428
            }
429
430
            $table = $tableDetails;
431
        }
432
433
        $tableDiff = new TableDiff($table->getName());
434
        $tableDiff->fromTable = $table;
0 ignored issues
show
Documentation Bug introduced by
It seems like $table can also be of type false. However, the property $fromTable is declared as type Doctrine\DBAL\Schema\Table. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
435
436
        return $tableDiff;
437
    }
438
439 37
    private function parseColumnCollationFromSQL(string $column, string $sql) : ?string
440
    {
441 37
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
442 37
                 . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}isx';
443
444 37
        if (preg_match($pattern, $sql, $match) !== 1) {
445 30
            return null;
446
        }
447
448 10
        return $match[1];
449
    }
450
451 62
    private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
452
    {
453 62
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
454 62
                 . '\W)(?:\(.*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}ix';
455
456 62
        if (preg_match($pattern, $sql, $match) !== 1) {
457 53
            return null;
458
        }
459
460 20
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
461
462 20
        return '' === $comment ? null : $comment;
463
    }
464
}
465