Completed
Pull Request — develop (#3511)
by Sergei
64:25
created

_getPortableTableColumnDefinition()   B

Complexity

Conditions 8
Paths 96

Size

Total Lines 53
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 8.0093

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 53
ccs 36
cts 38
cp 0.9474
rs 8.1475
c 0
b 0
f 0
cc 8
nc 96
nop 1
crap 8.0093

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
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Schema;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\DriverManager;
9
use Doctrine\DBAL\FetchMode;
10
use Doctrine\DBAL\Types\StringType;
11
use Doctrine\DBAL\Types\TextType;
12
use Doctrine\DBAL\Types\Type;
13
use const CASE_LOWER;
14
use function array_change_key_case;
15
use function array_reverse;
16
use function array_values;
17
use function count;
18
use function file_exists;
19
use function preg_match;
20
use function preg_match_all;
21
use function preg_quote;
22
use function preg_replace;
23
use function rtrim;
24
use function sprintf;
25
use function str_replace;
26
use function strpos;
27
use function strtolower;
28
use function trim;
29
use function unlink;
30
use function usort;
31
32
/**
33
 * Sqlite SchemaManager.
34
 */
35
class SqliteSchemaManager extends AbstractSchemaManager
36
{
37
    /**
38
     * {@inheritdoc}
39 86
     */
40
    public function dropDatabase($database)
41 86
    {
42 85
        if (! file_exists($database)) {
43
            return;
44
        }
45 86
46 86
        unlink($database);
47
    }
48
49
    /**
50
     * {@inheritdoc}
51 86
     */
52
    public function createDatabase($database)
53 86
    {
54 86
        $params  = $this->_conn->getParams();
55
        $driver  = $params['driver'];
56 86
        $options = [
57 86
            'driver' => $driver,
58
            'path' => $database,
59 86
        ];
60 86
        $conn    = DriverManager::getConnection($options);
61 86
        $conn->connect();
62 86
        $conn->close();
63
    }
64
65
    /**
66
     * {@inheritdoc}
67 84
     */
68
    public function renameTable($name, $newName)
69 84
    {
70 84
        $tableDiff            = new TableDiff($name);
71 84
        $tableDiff->fromTable = $this->listTableDetails($name);
72 84
        $tableDiff->newName   = $newName;
73 84
        $this->alterTable($tableDiff);
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
80
    {
81
        $tableDiff                     = $this->getTableDiffForAlterForeignKey($table);
82
        $tableDiff->addedForeignKeys[] = $foreignKey;
83
84
        $this->alterTable($tableDiff);
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
91
    {
92
        $tableDiff                       = $this->getTableDiffForAlterForeignKey($table);
93
        $tableDiff->changedForeignKeys[] = $foreignKey;
94
95
        $this->alterTable($tableDiff);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function dropForeignKey($foreignKey, $table)
102
    {
103
        $tableDiff                       = $this->getTableDiffForAlterForeignKey($table);
104
        $tableDiff->removedForeignKeys[] = $foreignKey;
105
106
        $this->alterTable($tableDiff);
107
    }
108
109
    /**
110
     * {@inheritdoc}
111 83
     */
112
    public function listTableForeignKeys($table, $database = null)
113 83
    {
114 83
        if ($database === null) {
115
            $database = $this->_conn->getDatabase();
116 83
        }
117 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

117
        /** @scrutinizer ignore-call */ 
118
        $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...
118
        $tableForeignKeys = $this->_conn->fetchAll($sql);
119 83
120 83
        if (! empty($tableForeignKeys)) {
121
            $createSql = $this->getCreateTableSQL($table);
122 83
123
            if ($createSql !== null && preg_match_all(
124
                '#
125
                    (?:CONSTRAINT\s+([^\s]+)\s+)?
126
                    (?:FOREIGN\s+KEY[^\)]+\)\s*)?
127
                    REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))?
128
                    (?:
129
                        [^,]*?
130
                        (NOT\s+DEFERRABLE|DEFERRABLE)
131
                        (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
132 83
                    )?#isx',
133 83
                $createSql,
134
                $match
135 83
            )) {
136 83
                $names      = array_reverse($match[1]);
137 83
                $deferrable = array_reverse($match[2]);
138
                $deferred   = array_reverse($match[3]);
139
            } else {
140
                $names = $deferrable = $deferred = [];
141
            }
142 83
143 83
            foreach ($tableForeignKeys as $key => $value) {
144 83
                $id                                        = $value['id'];
145 83
                $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && $names[$id] !== '' ? $names[$id] : $id;
146 83
                $tableForeignKeys[$key]['deferrable']      = isset($deferrable[$id]) && strtolower($deferrable[$id]) === 'deferrable';
147
                $tableForeignKeys[$key]['deferred']        = isset($deferred[$id]) && strtolower($deferred[$id]) === 'deferred';
148
            }
149
        }
150 83
151
        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
152
    }
153
154
    /**
155
     * {@inheritdoc}
156 144
     */
157
    protected function _getPortableTableDefinition($table)
158 144
    {
159
        return $table['name'];
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     *
165
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
166 137
     */
167
    protected function _getPortableTableIndexesList(array $tableIndexRows, string $tableName) : array
168 137
    {
169
        $indexBuffer = [];
170
171 137
        // fetch primary
172 2
        $stmt       = $this->_conn->executeQuery(sprintf(
173 137
            'PRAGMA TABLE_INFO (%s)',
174
            $this->_conn->quote($tableName)
175 137
        ));
176
        $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
177
178 84
        usort($indexArray, static function ($a, $b) {
179 84
            if ($a['pk'] === $b['pk']) {
180
                return $a['cid'] - $b['cid'];
181
            }
182 84
183 137
            return $a['pk'] - $b['pk'];
184 137
        });
185 137
        foreach ($indexArray as $indexColumnRow) {
186 89
            if ($indexColumnRow['pk'] === '0') {
187
                continue;
188
            }
189 132
190 132
            $indexBuffer[] = [
191
                'key_name' => 'primary',
192
                'primary' => true,
193 132
                'non_unique' => false,
194
                'column_name' => $indexColumnRow['name'],
195
            ];
196
        }
197
198 137
        // fetch regular indexes
199
        foreach ($tableIndexRows as $tableIndex) {
200 80
            // Ignore indexes with reserved names, e.g. autoindexes
201 80
            if (strpos($tableIndex['name'], 'sqlite_') === 0) {
202
                continue;
203
            }
204 71
205 71
            $keyName           = $tableIndex['name'];
206 71
            $idx               = [];
207 71
            $idx['key_name']   = $keyName;
208 71
            $idx['primary']    = false;
209
            $idx['non_unique'] = ! $tableIndex['unique'];
210 71
211
                $stmt       = $this->_conn->executeQuery(sprintf(
212 71
                    'PRAGMA INDEX_INFO (%s)',
213
                    $this->_conn->quote($keyName)
214 71
                ));
215
                $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
216 71
217 71
            foreach ($indexArray as $indexColumnRow) {
218 71
                $idx['column_name'] = $indexColumnRow['name'];
219
                $indexBuffer[]      = $idx;
220
            }
221
        }
222 137
223
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    protected function _getPortableTableIndexDefinition($tableIndex)
230
    {
231
        return [
232
            'name' => $tableIndex['name'],
233
            'unique' => (bool) $tableIndex['unique'],
234
        ];
235
    }
236
237
    /**
238
     * {@inheritdoc}
239 137
     */
240
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
241 137
    {
242
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
243
244 137
        // find column with autoincrement
245 137
        $autoincrementColumn = null;
246
        $autoincrementCount  = 0;
247 137
248 137
        foreach ($tableColumns as $tableColumn) {
249 89
            if ($tableColumn['pk'] === '0') {
250
                continue;
251
            }
252 132
253 132
            $autoincrementCount++;
254 71
            if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
255
                continue;
256
            }
257 132
258
            $autoincrementColumn = $tableColumn['name'];
259
        }
260 137
261 132
        if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
0 ignored issues
show
introduced by
The condition $autoincrementColumn !== null is always false.
Loading history...
262 132
            foreach ($list as $column) {
263 84
                if ($autoincrementColumn !== $column->getName()) {
264
                    continue;
265
                }
266 132
267
                $column->setAutoincrement(true);
268
            }
269
        }
270
271 137
        // inspect column collation and comments
272
        $createSql = $this->getCreateTableSQL($table) ?? '';
273 137
274 137
        foreach ($list as $columnName => $column) {
275
            $type = $column->getType();
276 137
277 84
            if ($type instanceof StringType || $type instanceof TextType) {
278
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
279
            }
280 137
281
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
282 137
283 137
            if ($comment === null) {
284
                continue;
285
            }
286 58
287
            $type = $this->extractDoctrineTypeFromComment($comment, '');
288 58
289 57
            if ($type !== '') {
290
                $column->setType(Type::getType($type));
291 57
292
                $comment = $this->removeDoctrineTypeFromComment($comment, $type);
293
            }
294 58
295
            $column->setComment($comment);
296
        }
297 137
298
        return $list;
299
    }
300
301
    /**
302
     * {@inheritdoc}
303 137
     */
304
    protected function _getPortableTableColumnDefinition($tableColumn)
305 137
    {
306 137
        preg_match('/^([^()]*)\\s*(\\(((\\d+)(,\\s*(\\d+))?)\\))?/', $tableColumn['type'], $matches);
307 137
308 84
        $dbType = trim(strtolower($matches[1]));
309 84
310
        $length = $precision = $scale = $unsigned = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $unsigned is dead and can be removed.
Loading history...
311
312 137
        $fixed = $unsigned = false;
313 137
314 137
        if (count($matches) >= 6) {
315
            $precision = (int) $matches[4];
316 137
            $scale     = (int) $matches[6];
317 36
        } elseif (count($matches) >= 4) {
318 36
            $length = (int) $matches[4];
319
        }
320
321 137
        if (strpos($dbType, ' unsigned') !== false) {
322 137
            $dbType   = str_replace(' unsigned', '', $dbType);
323 137
            $unsigned = true;
324 137
        }
325 70
326
        $type    = $this->_platform->getDoctrineTypeMapping($dbType);
327 137
        $default = $tableColumn['dflt_value'];
328
        if ($default === 'NULL') {
329 89
            $default = null;
330
        }
331 137
        if ($default !== null) {
332
            // SQLite returns strings wrapped in single quotes, so we need to strip them
333 137
            $default = preg_replace("/^'(.*)'$/", '\1', $default);
334
        }
335
        $notnull = (bool) $tableColumn['notnull'];
336
337 137
        if (! isset($tableColumn['name'])) {
338 137
            $tableColumn['name'] = '';
339
        }
340 135
341 4
        if ($dbType === 'char') {
342 69
            $fixed = true;
343 69
        }
344 4
345 4
        $options = [
346 4
            'length'   => $length,
347 4
            'unsigned' => $unsigned,
348 4
            'fixed'    => $fixed,
349 70
            'notnull'  => $notnull,
350 70
            'default'  => $default,
351
            'precision' => $precision,
352
            'scale'     => $scale,
353 70
            'autoincrement' => false,
354
        ];
355 70
356 70
        return new Column($tableColumn['name'], Type::getType($type), $options);
357
    }
358
359
    /**
360 137
     * {@inheritdoc}
361 137
     */
362 137
    protected function _getPortableViewDefinition($view)
363 137
    {
364 137
        return new View($view['name'], $view['sql']);
365 137
    }
366 137
367
    /**
368
     * {@inheritdoc}
369
     */
370 137
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
371
    {
372
        $list = [];
373
        foreach ($tableForeignKeys as $value) {
374
            $value = array_change_key_case($value, CASE_LOWER);
375
            $name  = $value['constraint_name'];
376 61
            if (! isset($list[$name])) {
377
                if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
378 61
                    $value['on_delete'] = null;
379
                }
380
                if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
381
                    $value['on_update'] = null;
382
                }
383
384 83
                $list[$name] = [
385
                    'name' => $name,
386 83
                    'local' => [],
387 83
                    'foreign' => [],
388 83
                    'foreignTable' => $value['table'],
389 83
                    'onDelete' => $value['on_delete'],
390 83
                    'onUpdate' => $value['on_update'],
391 83
                    'deferrable' => $value['deferrable'],
392
                    'deferred'=> $value['deferred'],
393
                ];
394 83
            }
395
            $list[$name]['local'][]   = $value['from'];
396
            $list[$name]['foreign'][] = $value['to'];
397
        }
398 83
399 83
        $result = [];
400
        foreach ($list as $constraint) {
401
            $result[] = new ForeignKeyConstraint(
402 83
                array_values($constraint['local']),
403 83
                $constraint['foreignTable'],
404 83
                array_values($constraint['foreign']),
405 83
                $constraint['name'],
406 83
                [
407
                    'onDelete' => $constraint['onDelete'],
408
                    'onUpdate' => $constraint['onUpdate'],
409 83
                    'deferrable' => $constraint['deferrable'],
410 83
                    'deferred'=> $constraint['deferred'],
411
                ]
412
            );
413 83
        }
414 83
415 83
        return $result;
416 83
    }
417 83
418 83
    /**
419 83
     * @param Table|string $table
420
     *
421 83
     * @return TableDiff
422 83
     *
423 83
     * @throws DBALException
424 83
     */
425
    private function getTableDiffForAlterForeignKey($table)
426
    {
427
        if (! $table instanceof Table) {
428
            $tableDetails = $this->tryMethod('listTableDetails', $table);
429 83
430
            if ($tableDetails === false) {
431
                throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
432
            }
433
434
            $table = $tableDetails;
435
        }
436
437
        $tableDiff            = new TableDiff($table->getName());
438
        $tableDiff->fromTable = $table;
439
440
        return $tableDiff;
441
    }
442
443
    private function parseColumnCollationFromSQL(string $column, string $sql) : ?string
444
    {
445
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
446
            . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
447
448
        if (preg_match($pattern, $sql, $match) !== 1) {
449
            return null;
450
        }
451
452
        return $match[1];
453
    }
454
455
    private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
456
    {
457 905
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
458
            . '\W)(?:\(.*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
459 905
460 905
        if (preg_match($pattern, $sql, $match) !== 1) {
461
            return null;
462 905
        }
463 848
464
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
465
466 898
        return $comment === '' ? null : $comment;
467
    }
468
469 619
    private function getCreateTableSQL(string $table) : ?string
470
    {
471 619
        return $this->_conn->fetchColumn(
472 619
            <<<'SQL'
473
SELECT sql
474 619
  FROM (
475 608
      SELECT *
476
        FROM sqlite_master
477
   UNION ALL
478 575
      SELECT *
479
        FROM sqlite_temp_master
480 575
  )
481
WHERE type = 'table'
482
AND name = ?
483 137
SQL
484
            ,
485 137
            [$table]
486
        ) ?: null;
487 137
    }
488
}
489