Completed
Push — develop ( 72ba3e...de019a )
by Marco
25s queued 12s
created

_getPortableTableIndexesList()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 8.0018

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 57
ccs 31
cts 32
cp 0.9688
rs 8.1475
c 0
b 0
f 0
cc 8
nc 18
nop 2
crap 8.0018

How to fix   Long Method   

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
namespace Doctrine\DBAL\Schema;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\DriverManager;
7
use Doctrine\DBAL\FetchMode;
8
use Doctrine\DBAL\Types\StringType;
9
use Doctrine\DBAL\Types\TextType;
10
use Doctrine\DBAL\Types\Type;
11
use const CASE_LOWER;
12
use function array_change_key_case;
13
use function array_map;
14
use function array_reverse;
15
use function array_values;
16
use function explode;
17
use function file_exists;
18
use function preg_match;
19
use function preg_match_all;
20
use function preg_quote;
21
use function preg_replace;
22
use function rtrim;
23
use function sprintf;
24
use function str_replace;
25
use function strpos;
26
use function strtolower;
27
use function trim;
28
use function unlink;
29
use function usort;
30
31
/**
32
 * Sqlite SchemaManager.
33
 */
34
class SqliteSchemaManager extends AbstractSchemaManager
35
{
36
    /**
37
     * {@inheritdoc}
38
     */
39 86
    public function dropDatabase($database)
40
    {
41 86
        if (! file_exists($database)) {
42 85
            return;
43
        }
44
45 86
        unlink($database);
46 86
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 86
    public function createDatabase($database)
52
    {
53 86
        $params  = $this->_conn->getParams();
54 86
        $driver  = $params['driver'];
55
        $options = [
56 86
            'driver' => $driver,
57 86
            'path' => $database,
58
        ];
59 86
        $conn    = DriverManager::getConnection($options);
60 86
        $conn->connect();
61 86
        $conn->close();
62 86
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 84
    public function renameTable($name, $newName)
68
    {
69 84
        $tableDiff            = new TableDiff($name);
70 84
        $tableDiff->fromTable = $this->listTableDetails($name);
71 84
        $tableDiff->newName   = $newName;
72 84
        $this->alterTable($tableDiff);
73 84
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
79
    {
80
        $tableDiff                     = $this->getTableDiffForAlterForeignKey($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($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($table);
103
        $tableDiff->removedForeignKeys[] = $foreignKey;
104
105
        $this->alterTable($tableDiff);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 83
    public function listTableForeignKeys($table, $database = null)
112
    {
113 83
        if ($database === null) {
114 83
            $database = $this->_conn->getDatabase();
115
        }
116 83
        $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 83
        $tableForeignKeys = $this->_conn->fetchAll($sql);
118
119 83
        if (! empty($tableForeignKeys)) {
120 83
            $createSql = $this->getCreateTableSQL($table);
121
122 83
            if ($createSql !== null && preg_match_all(
123
                '#
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 83
                $createSql,
133 83
                $match
134
            )) {
135 83
                $names      = array_reverse($match[1]);
136 83
                $deferrable = array_reverse($match[2]);
137 83
                $deferred   = array_reverse($match[3]);
138
            } else {
139
                $names = $deferrable = $deferred = [];
140
            }
141
142 83
            foreach ($tableForeignKeys as $key => $value) {
143 83
                $id                                        = $value['id'];
144 83
                $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && $names[$id] !== '' ? $names[$id] : $id;
145 83
                $tableForeignKeys[$key]['deferrable']      = isset($deferrable[$id]) && strtolower($deferrable[$id]) === 'deferrable';
146 83
                $tableForeignKeys[$key]['deferred']        = isset($deferred[$id]) && strtolower($deferred[$id]) === 'deferred';
147
            }
148
        }
149
150 83
        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 144
    protected function _getPortableTableDefinition($table)
157
    {
158 144
        return $table['name'];
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     *
164
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
165
     */
166 137
    protected function _getPortableTableIndexesList(array $tableIndexRows, string $tableName) : array
167
    {
168 137
        $indexBuffer = [];
169
170
        // fetch primary
171 137
        $stmt       = $this->_conn->executeQuery(sprintf(
172 2
            'PRAGMA TABLE_INFO (%s)',
173 137
            $this->_conn->quote($tableName)
174
        ));
175 137
        $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
176
177
        usort($indexArray, static function ($a, $b) {
178 84
            if ($a['pk'] === $b['pk']) {
179 84
                return $a['cid'] - $b['cid'];
180
            }
181
182 84
            return $a['pk'] - $b['pk'];
183 137
        });
184 137
        foreach ($indexArray as $indexColumnRow) {
185 137
            if ($indexColumnRow['pk'] === '0') {
186 89
                continue;
187
            }
188
189 132
            $indexBuffer[] = [
190 132
                'key_name' => 'primary',
191
                'primary' => true,
192
                'non_unique' => false,
193 132
                'column_name' => $indexColumnRow['name'],
194
            ];
195
        }
196
197
        // fetch regular indexes
198 137
        foreach ($tableIndexRows as $tableIndex) {
199
            // Ignore indexes with reserved names, e.g. autoindexes
200 80
            if (strpos($tableIndex['name'], 'sqlite_') === 0) {
201 80
                continue;
202
            }
203
204 71
            $keyName           = $tableIndex['name'];
205 71
            $idx               = [];
206 71
            $idx['key_name']   = $keyName;
207 71
            $idx['primary']    = false;
208 71
            $idx['non_unique'] = $tableIndex['unique']?false:true;
209
210 71
                $stmt       = $this->_conn->executeQuery(sprintf(
211
                    'PRAGMA INDEX_INFO (%s)',
212 71
                    $this->_conn->quote($keyName)
213
                ));
214 71
                $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
215
216 71
            foreach ($indexArray as $indexColumnRow) {
217 71
                $idx['column_name'] = $indexColumnRow['name'];
218 71
                $indexBuffer[]      = $idx;
219
            }
220
        }
221
222 137
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    protected function _getPortableTableIndexDefinition($tableIndex)
229
    {
230
        return [
231
            'name' => $tableIndex['name'],
232
            'unique' => (bool) $tableIndex['unique'],
233
        ];
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239 137
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
240
    {
241 137
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
242
243
        // find column with autoincrement
244 137
        $autoincrementColumn = null;
245 137
        $autoincrementCount  = 0;
246
247 137
        foreach ($tableColumns as $tableColumn) {
248 137
            if ($tableColumn['pk'] === '0') {
249 89
                continue;
250
            }
251
252 132
            $autoincrementCount++;
253 132
            if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
254 71
                continue;
255
            }
256
257 132
            $autoincrementColumn = $tableColumn['name'];
258
        }
259
260 137
        if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
0 ignored issues
show
introduced by
The condition $autoincrementColumn !== null is always false.
Loading history...
261 132
            foreach ($list as $column) {
262 132
                if ($autoincrementColumn !== $column->getName()) {
263 84
                    continue;
264
                }
265
266 132
                $column->setAutoincrement(true);
267
            }
268
        }
269
270
        // inspect column collation and comments
271 137
        $createSql = $this->getCreateTableSQL($table) ?? '';
272
273 137
        foreach ($list as $columnName => $column) {
274 137
            $type = $column->getType();
275
276 137
            if ($type instanceof StringType || $type instanceof TextType) {
277 84
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
278
            }
279
280 137
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
281
282 137
            if ($comment === null) {
283 137
                continue;
284
            }
285
286 58
            $type = $this->extractDoctrineTypeFromComment($comment, '');
287
288 58
            if ($type !== '') {
289 57
                $column->setType(Type::getType($type));
290
291 57
                $comment = $this->removeDoctrineTypeFromComment($comment, $type);
292
            }
293
294 58
            $column->setComment($comment);
295
        }
296
297 137
        return $list;
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303 137
    protected function _getPortableTableColumnDefinition($tableColumn)
304
    {
305 137
        $parts               = explode('(', $tableColumn['type']);
306 137
        $tableColumn['type'] = trim($parts[0]);
307 137
        if (isset($parts[1])) {
308 84
            $length                = trim($parts[1], ')');
309 84
            $tableColumn['length'] = $length;
310
        }
311
312 137
        $dbType   = strtolower($tableColumn['type']);
313 137
        $length   = $tableColumn['length'] ?? null;
314 137
        $unsigned = false;
315
316 137
        if (strpos($dbType, ' unsigned') !== false) {
317 36
            $dbType   = str_replace(' unsigned', '', $dbType);
318 36
            $unsigned = true;
319
        }
320
321 137
        $fixed   = false;
322 137
        $type    = $this->_platform->getDoctrineTypeMapping($dbType);
323 137
        $default = $tableColumn['dflt_value'];
324 137
        if ($default === 'NULL') {
325 70
            $default = null;
326
        }
327 137
        if ($default !== null) {
328
            // SQLite returns strings wrapped in single quotes, so we need to strip them
329 89
            $default = preg_replace("/^'(.*)'$/", '\1', $default);
330
        }
331 137
        $notnull = (bool) $tableColumn['notnull'];
332
333 137
        if (! isset($tableColumn['name'])) {
334
            $tableColumn['name'] = '';
335
        }
336
337 137
        $precision = null;
338 137
        $scale     = null;
339
340 135
        switch ($dbType) {
341 4
            case 'char':
342 69
                $fixed = true;
343 69
                break;
344 4
            case 'float':
345 4
            case 'double':
346 4
            case 'real':
347 4
            case 'decimal':
348 4
            case 'numeric':
349 70
                if (isset($tableColumn['length'])) {
350 70
                    if (strpos($tableColumn['length'], ',') === false) {
351
                        $tableColumn['length'] .= ',0';
352
                    }
353 70
                    [$precision, $scale] = array_map('trim', explode(',', $tableColumn['length']));
354
                }
355 70
                $length = null;
356 70
                break;
357
        }
358
359
        $options = [
360 137
            'length'   => $length,
361 137
            'unsigned' => (bool) $unsigned,
362 137
            'fixed'    => $fixed,
363 137
            'notnull'  => $notnull,
364 137
            'default'  => $default,
365 137
            'precision' => $precision,
366 137
            'scale'     => $scale,
367
            'autoincrement' => false,
368
        ];
369
370 137
        return new Column($tableColumn['name'], Type::getType($type), $options);
371
    }
372
373
    /**
374
     * {@inheritdoc}
375
     */
376 61
    protected function _getPortableViewDefinition($view)
377
    {
378 61
        return new View($view['name'], $view['sql']);
379
    }
380
381
    /**
382
     * {@inheritdoc}
383
     */
384 83
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
385
    {
386 83
        $list = [];
387 83
        foreach ($tableForeignKeys as $value) {
388 83
            $value = array_change_key_case($value, CASE_LOWER);
389 83
            $name  = $value['constraint_name'];
390 83
            if (! isset($list[$name])) {
391 83
                if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
392
                    $value['on_delete'] = null;
393
                }
394 83
                if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
395
                    $value['on_update'] = null;
396
                }
397
398 83
                $list[$name] = [
399 83
                    'name' => $name,
400
                    'local' => [],
401
                    'foreign' => [],
402 83
                    'foreignTable' => $value['table'],
403 83
                    'onDelete' => $value['on_delete'],
404 83
                    'onUpdate' => $value['on_update'],
405 83
                    'deferrable' => $value['deferrable'],
406 83
                    'deferred'=> $value['deferred'],
407
                ];
408
            }
409 83
            $list[$name]['local'][]   = $value['from'];
410 83
            $list[$name]['foreign'][] = $value['to'];
411
        }
412
413 83
        $result = [];
414 83
        foreach ($list as $constraint) {
415 83
            $result[] = new ForeignKeyConstraint(
416 83
                array_values($constraint['local']),
417 83
                $constraint['foreignTable'],
418 83
                array_values($constraint['foreign']),
419 83
                $constraint['name'],
420
                [
421 83
                    'onDelete' => $constraint['onDelete'],
422 83
                    'onUpdate' => $constraint['onUpdate'],
423 83
                    'deferrable' => $constraint['deferrable'],
424 83
                    'deferred'=> $constraint['deferred'],
425
                ]
426
            );
427
        }
428
429 83
        return $result;
430
    }
431
432
    /**
433
     * @param Table|string $table
434
     *
435
     * @return TableDiff
436
     *
437
     * @throws DBALException
438
     */
439
    private function getTableDiffForAlterForeignKey($table)
440
    {
441
        if (! $table instanceof Table) {
442
            $tableDetails = $this->tryMethod('listTableDetails', $table);
443
444
            if ($tableDetails === false) {
445
                throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
446
            }
447
448
            $table = $tableDetails;
449
        }
450
451
        $tableDiff            = new TableDiff($table->getName());
452
        $tableDiff->fromTable = $table;
453
454
        return $tableDiff;
455
    }
456
457 905
    private function parseColumnCollationFromSQL(string $column, string $sql) : ?string
458
    {
459 905
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
460 905
            . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
461
462 905
        if (preg_match($pattern, $sql, $match) !== 1) {
463 848
            return null;
464
        }
465
466 898
        return $match[1];
467
    }
468
469 619
    private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
470
    {
471 619
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
472 619
            . '\W)(?:\(.*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
473
474 619
        if (preg_match($pattern, $sql, $match) !== 1) {
475 608
            return null;
476
        }
477
478 575
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
479
480 575
        return $comment === '' ? null : $comment;
481
    }
482
483 137
    private function getCreateTableSQL(string $table) : ?string
484
    {
485 137
        return $this->_conn->fetchColumn(
486
            <<<'SQL'
487 137
SELECT sql
488
  FROM (
489
      SELECT *
490
        FROM sqlite_master
491
   UNION ALL
492
      SELECT *
493
        FROM sqlite_temp_master
494
  )
495
WHERE type = 'table'
496
AND name = ?
497
SQL
498
            ,
499 137
            [$table]
500 137
        ) ?: null;
501
    }
502
}
503