Completed
Pull Request — develop (#3565)
by Jonathan
13:02
created

_getPortableTableColumnDefinition()   B

Complexity

Conditions 9
Paths 144

Size

Total Lines 57
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 9.002

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 57
ccs 33
cts 34
cp 0.9706
rs 7.6888
c 0
b 0
f 0
cc 9
nc 144
nop 1
crap 9.002

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