MigrateController   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 535
Duplicated Lines 0 %

Test Coverage

Coverage 90.59%

Importance

Changes 0
Metric Value
eloc 236
dl 0
loc 535
ccs 231
cts 255
cp 0.9059
rs 2.96
c 0
b 0
f 0
wmc 68

17 Methods

Rating   Name   Duplication   Size   Complexity  
A removeMigrationHistory() 0 6 1
A getMigrationNameLimit() 0 11 4
A normalizeTableName() 0 11 3
A addDefaultPrimaryKey() 0 8 4
A splitFieldIntoChunks() 0 20 6
A beforeAction() 0 8 2
A options() 0 8 2
B getMigrationHistory() 0 52 9
A generateTableName() 0 7 2
A createMigration() 0 8 1
A truncateDatabase() 0 24 6
A createMigrationHistoryTable() 0 13 1
A addMigrationHistory() 0 7 1
A optionAliases() 0 10 1
A isViewRelated() 0 14 3
C generateMigrationSourceCode() 0 98 15
B parseFields() 0 38 7

How to fix   Complexity   

Complex Class

Complex classes like MigrateController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MigrateController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @link https://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license https://www.yiiframework.com/license/
7
 */
8
9
namespace yii\console\controllers;
10
11
use Yii;
12
use yii\db\Connection;
13
use yii\db\Query;
14
use yii\di\Instance;
15
use yii\helpers\ArrayHelper;
16
use yii\helpers\Console;
17
use yii\helpers\Inflector;
18
19
/**
20
 * Manages application migrations.
21
 *
22
 * A migration means a set of persistent changes to the application environment
23
 * that is shared among different developers. For example, in an application
24
 * backed by a database, a migration may refer to a set of changes to
25
 * the database, such as creating a new table, adding a new table column.
26
 *
27
 * This command provides support for tracking the migration history, upgrading
28
 * or downloading with migrations, and creating new migration skeletons.
29
 *
30
 * The migration history is stored in a database table named
31
 * as [[migrationTable]]. The table will be automatically created the first time
32
 * this command is executed, if it does not exist. You may also manually
33
 * create it as follows:
34
 *
35
 * ```sql
36
 * CREATE TABLE migration (
37
 *     version varchar(180) PRIMARY KEY,
38
 *     apply_time integer
39
 * )
40
 * ```
41
 *
42
 * Below are some common usages of this command:
43
 *
44
 * ```
45
 * # creates a new migration named 'create_user_table'
46
 * yii migrate/create create_user_table
47
 *
48
 * # applies ALL new migrations
49
 * yii migrate
50
 *
51
 * # reverts the last applied migration
52
 * yii migrate/down
53
 * ```
54
 *
55
 * Since 2.0.10 you can use namespaced migrations. In order to enable this feature you should configure [[migrationNamespaces]]
56
 * property for the controller at application configuration:
57
 *
58
 * ```php
59
 * return [
60
 *     'controllerMap' => [
61
 *         'migrate' => [
62
 *             'class' => 'yii\console\controllers\MigrateController',
63
 *             'migrationNamespaces' => [
64
 *                 'app\migrations',
65
 *                 'some\extension\migrations',
66
 *             ],
67
 *             //'migrationPath' => null, // allows to disable not namespaced migration completely
68
 *         ],
69
 *     ],
70
 * ];
71
 * ```
72
 *
73
 * @author Qiang Xue <[email protected]>
74
 * @since 2.0
75
 */
76
class MigrateController extends BaseMigrateController
77
{
78
    /**
79
     * Maximum length of a migration name.
80
     * @since 2.0.13
81
     */
82
    const MAX_NAME_LENGTH = 180;
83
84
    /**
85
     * @var string the name of the table for keeping applied migration information.
86
     */
87
    public $migrationTable = '{{%migration}}';
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public $templateFile = '@yii/views/migration.php';
92
    /**
93
     * @var array a set of template paths for generating migration code automatically.
94
     *
95
     * The key is the template type, the value is a path or the alias. Supported types are:
96
     * - `create_table`: table creating template
97
     * - `drop_table`: table dropping template
98
     * - `add_column`: adding new column template
99
     * - `drop_column`: dropping column template
100
     * - `create_junction`: create junction template
101
     *
102
     * @since 2.0.7
103
     */
104
    public $generatorTemplateFiles = [
105
        'create_table' => '@yii/views/createTableMigration.php',
106
        'drop_table' => '@yii/views/dropTableMigration.php',
107
        'add_column' => '@yii/views/addColumnMigration.php',
108
        'drop_column' => '@yii/views/dropColumnMigration.php',
109
        'create_junction' => '@yii/views/createTableMigration.php',
110
    ];
111
    /**
112
     * @var bool indicates whether the table names generated should consider
113
     * the `tablePrefix` setting of the DB connection. For example, if the table
114
     * name is `post` the generator wil return `{{%post}}`.
115
     * @since 2.0.8
116
     */
117
    public $useTablePrefix = true;
118
    /**
119
     * @var array column definition strings used for creating migration code.
120
     *
121
     * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Delimiter is `,`.
122
     * For example, `--fields="name:string(12):notNull:unique"`
123
     * produces a string column of size 12 which is not null and unique values.
124
     *
125
     * Note: primary key is added automatically and is named id by default.
126
     * If you want to use another name you may specify it explicitly like
127
     * `--fields="id_key:primaryKey,name:string(12):notNull:unique"`
128
     * @since 2.0.7
129
     */
130
    public $fields = [];
131
    /**
132
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection to use
133
     * when applying migrations. Starting from version 2.0.3, this can also be a configuration array
134
     * for creating the object.
135
     */
136
    public $db = 'db';
137
    /**
138
     * @var string the comment for the table being created.
139
     * @since 2.0.14
140
     */
141
    public $comment = '';
142
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 130
    public function options($actionID)
148
    {
149 130
        return array_merge(
150 130
            parent::options($actionID),
151 130
            ['migrationTable', 'db'], // global for all actions
152 130
            $actionID === 'create'
153 85
                ? ['templateFile', 'fields', 'useTablePrefix', 'comment']
154 130
                : []
155 130
        );
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     * @since 2.0.8
161
     */
162
    public function optionAliases()
163
    {
164
        return array_merge(parent::optionAliases(), [
165
            'C' => 'comment',
166
            'f' => 'fields',
167
            'p' => 'migrationPath',
168
            't' => 'migrationTable',
169
            'F' => 'templateFile',
170
            'P' => 'useTablePrefix',
171
            'c' => 'compact',
172
        ]);
173
    }
174
175
    /**
176
     * This method is invoked right before an action is to be executed (after all possible filters.)
177
     * It checks the existence of the [[migrationPath]].
178
     * @param \yii\base\Action $action the action to be executed.
179
     * @return bool whether the action should continue to be executed.
180
     */
181 141
    public function beforeAction($action)
182
    {
183 141
        if (parent::beforeAction($action)) {
184 141
            $this->db = Instance::ensure($this->db, Connection::className());
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

184
            $this->db = Instance::ensure($this->db, /** @scrutinizer ignore-deprecated */ Connection::className());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
185 141
            return true;
186
        }
187
188
        return false;
189
    }
190
191
    /**
192
     * Creates a new migration instance.
193
     * @param string $class the migration class name
194
     * @return \yii\db\Migration the migration instance
195
     */
196 51
    protected function createMigration($class)
197
    {
198 51
        $this->includeMigrationFile($class);
199
200 51
        return Yii::createObject([
201 51
            'class' => $class,
202 51
            'db' => $this->db,
203 51
            'compact' => $this->compact,
204 51
        ]);
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 57
    protected function getMigrationHistory($limit)
211
    {
212 57
        if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
213 30
            $this->createMigrationHistoryTable();
214
        }
215 57
        $query = (new Query())
216 57
            ->select(['version', 'apply_time'])
217 57
            ->from($this->migrationTable)
218 57
            ->orderBy(['apply_time' => SORT_DESC, 'version' => SORT_DESC]);
219
220 57
        if (empty($this->migrationNamespaces)) {
221 50
            $query->limit($limit);
222 50
            $rows = $query->all($this->db);
223 50
            $history = ArrayHelper::map($rows, 'version', 'apply_time');
224 50
            unset($history[self::BASE_MIGRATION]);
225 50
            return $history;
226
        }
227
228 7
        $rows = $query->all($this->db);
229
230 7
        $history = [];
231 7
        foreach ($rows as $key => $row) {
232 7
            if ($row['version'] === self::BASE_MIGRATION) {
233 7
                continue;
234
            }
235 4
            if (preg_match('/m?(\d{6}_?\d{6})(\D.*)?$/is', $row['version'], $matches)) {
236 4
                $time = str_replace('_', '', $matches[1]);
237 4
                $row['canonicalVersion'] = $time;
238
            } else {
239
                $row['canonicalVersion'] = $row['version'];
240
            }
241 4
            $row['apply_time'] = (int) $row['apply_time'];
242 4
            $history[] = $row;
243
        }
244
245 7
        usort($history, function ($a, $b) {
246 4
            if ($a['apply_time'] === $b['apply_time']) {
247 4
                if (($compareResult = strcasecmp($b['canonicalVersion'], $a['canonicalVersion'])) !== 0) {
248 2
                    return $compareResult;
249
                }
250
251 2
                return strcasecmp($b['version'], $a['version']);
252
            }
253
254 1
            return ($a['apply_time'] > $b['apply_time']) ? -1 : +1;
255 7
        });
256
257 7
        $history = array_slice($history, 0, $limit);
258
259 7
        $history = ArrayHelper::map($history, 'version', 'apply_time');
260
261 7
        return $history;
262
    }
263
264
    /**
265
     * Creates the migration history table.
266
     */
267 30
    protected function createMigrationHistoryTable()
268
    {
269 30
        $tableName = $this->db->schema->getRawTableName($this->migrationTable);
270 30
        $this->stdout("Creating migration history table \"$tableName\"...", Console::FG_YELLOW);
271 30
        $this->db->createCommand()->createTable($this->migrationTable, [
272 30
            'version' => 'varchar(' . static::MAX_NAME_LENGTH . ') NOT NULL PRIMARY KEY',
273 30
            'apply_time' => 'integer',
274 30
        ])->execute();
275 30
        $this->db->createCommand()->insert($this->migrationTable, [
276 30
            'version' => self::BASE_MIGRATION,
277 30
            'apply_time' => time(),
278 30
        ])->execute();
279 30
        $this->stdout("Done.\n", Console::FG_GREEN);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285 53
    protected function addMigrationHistory($version)
286
    {
287 53
        $command = $this->db->createCommand();
288 53
        $command->insert($this->migrationTable, [
289 53
            'version' => $version,
290 53
            'apply_time' => time(),
291 53
        ])->execute();
292
    }
293
294
    /**
295
     * {@inheritdoc}
296
     * @since 2.0.13
297
     */
298 2
    protected function truncateDatabase()
299
    {
300 2
        $db = $this->db;
301 2
        $schemas = $db->schema->getTableSchemas();
302
303
        // First drop all foreign keys,
304 2
        foreach ($schemas as $schema) {
305 2
            foreach ($schema->foreignKeys as $name => $foreignKey) {
306 1
                $db->createCommand()->dropForeignKey($name, $schema->name)->execute();
307 1
                $this->stdout("Foreign key $name dropped.\n");
308
            }
309
        }
310
311
        // Then drop the tables:
312 2
        foreach ($schemas as $schema) {
313
            try {
314 2
                $db->createCommand()->dropTable($schema->name)->execute();
315 2
                $this->stdout("Table {$schema->name} dropped.\n");
316 2
            } catch (\Exception $e) {
317 2
                if ($this->isViewRelated($e->getMessage())) {
318 2
                    $db->createCommand()->dropView($schema->name)->execute();
319 2
                    $this->stdout("View {$schema->name} dropped.\n");
320
                } else {
321
                    $this->stdout("Cannot drop {$schema->name} Table .\n");
322
                }
323
            }
324
        }
325
    }
326
327
    /**
328
     * Determines whether the error message is related to deleting a view or not
329
     * @param string $errorMessage
330
     * @return bool
331
     */
332 2
    private function isViewRelated($errorMessage)
333
    {
334 2
        $dropViewErrors = [
335 2
            'DROP VIEW to delete view', // SQLite
336 2
            'SQLSTATE[42S02]', // MySQL
337 2
        ];
338
339 2
        foreach ($dropViewErrors as $dropViewError) {
340 2
            if (strpos($errorMessage, $dropViewError) !== false) {
341 2
                return true;
342
            }
343
        }
344
345
        return false;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 42
    protected function removeMigrationHistory($version)
352
    {
353 42
        $command = $this->db->createCommand();
354 42
        $command->delete($this->migrationTable, [
355 42
            'version' => $version,
356 42
        ])->execute();
357
    }
358
359
    private $_migrationNameLimit;
360
361
    /**
362
     * {@inheritdoc}
363
     * @since 2.0.13
364
     */
365 136
    protected function getMigrationNameLimit()
366
    {
367 136
        if ($this->_migrationNameLimit !== null) {
368 8
            return $this->_migrationNameLimit;
369
        }
370 136
        $tableSchema = $this->db->schema ? $this->db->schema->getTableSchema($this->migrationTable, true) : null;
371 136
        if ($tableSchema !== null) {
372 52
            return $this->_migrationNameLimit = $tableSchema->columns['version']->size;
373
        }
374
375 84
        return static::MAX_NAME_LENGTH;
376
    }
377
378
    /**
379
     * Normalizes table name for generator.
380
     * When name is preceded with underscore name case is kept - otherwise it's converted from camelcase to underscored.
381
     * Last underscore is always trimmed so if there should be underscore at the end of name use two of them.
382
     * @param string $name
383
     * @return string
384
     */
385 80
    private function normalizeTableName($name)
386
    {
387 80
        if (substr($name, -1) === '_') {
388 60
            $name = substr($name, 0, -1);
389
        }
390
391 80
        if (strncmp($name, '_', 1) === 0) {
392 56
            return substr($name, 1);
393
        }
394
395 26
        return Inflector::underscore($name);
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     * @since 2.0.8
401
     */
402 84
    protected function generateMigrationSourceCode($params)
403
    {
404 84
        $parsedFields = $this->parseFields();
405 84
        $fields = $parsedFields['fields'];
406 84
        $foreignKeys = $parsedFields['foreignKeys'];
407
408 84
        $name = $params['name'];
409 84
        if ($params['namespace']) {
410 82
            $name = substr($name, (strrpos($name, '\\') ?: -1) + 1);
411
        }
412
413 84
        $templateFile = $this->templateFile;
414 84
        $table = null;
415 84
        if (preg_match('/^create_?junction_?(?:table)?_?(?:for)?(.+)_?and(.+)_?tables?$/i', $name, $matches)) {
416 15
            $templateFile = $this->generatorTemplateFiles['create_junction'];
417 15
            $firstTable = $this->normalizeTableName($matches[1]);
418 15
            $secondTable = $this->normalizeTableName($matches[2]);
419
420 15
            $fields = array_merge(
421 15
                [
422 15
                    [
423 15
                        'property' => $firstTable . '_id',
424 15
                        'decorators' => 'integer()',
425 15
                    ],
426 15
                    [
427 15
                        'property' => $secondTable . '_id',
428 15
                        'decorators' => 'integer()',
429 15
                    ],
430 15
                ],
431 15
                $fields,
432 15
                [
433 15
                    [
434 15
                        'property' => 'PRIMARY KEY(' .
435 15
                            $firstTable . '_id, ' .
436 15
                            $secondTable . '_id)',
437 15
                    ],
438 15
                ]
439 15
            );
440
441 15
            $foreignKeys[$firstTable . '_id']['table'] = $firstTable;
442 15
            $foreignKeys[$secondTable . '_id']['table'] = $secondTable;
443 15
            $foreignKeys[$firstTable . '_id']['column'] = null;
444 15
            $foreignKeys[$secondTable . '_id']['column'] = null;
445 15
            $table = $firstTable . '_' . $secondTable;
446 69
        } elseif (preg_match('/^add(.+)columns?_?to(.+)table$/i', $name, $matches)) {
447 15
            $templateFile = $this->generatorTemplateFiles['add_column'];
448 15
            $table = $this->normalizeTableName($matches[2]);
449 54
        } elseif (preg_match('/^drop(.+)columns?_?from(.+)table$/i', $name, $matches)) {
450 11
            $templateFile = $this->generatorTemplateFiles['drop_column'];
451 11
            $table = $this->normalizeTableName($matches[2]);
452 43
        } elseif (preg_match('/^create(.+)table$/i', $name, $matches)) {
453 27
            $this->addDefaultPrimaryKey($fields);
454 27
            $templateFile = $this->generatorTemplateFiles['create_table'];
455 27
            $table = $this->normalizeTableName($matches[1]);
456 16
        } elseif (preg_match('/^drop(.+)table$/i', $name, $matches)) {
457 12
            $this->addDefaultPrimaryKey($fields);
458 12
            $templateFile = $this->generatorTemplateFiles['drop_table'];
459 12
            $table = $this->normalizeTableName($matches[1]);
460
        }
461
462 84
        foreach ($foreignKeys as $column => $foreignKey) {
463 23
            $relatedColumn = $foreignKey['column'];
464 23
            $relatedTable = $foreignKey['table'];
465
            // Since 2.0.11 if related column name is not specified,
466
            // we're trying to get it from table schema
467
            // @see https://github.com/yiisoft/yii2/issues/12748
468 23
            if ($relatedColumn === null) {
469 23
                $relatedColumn = 'id';
470
                try {
471 23
                    $this->db = Instance::ensure($this->db, Connection::className());
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

471
                    $this->db = Instance::ensure($this->db, /** @scrutinizer ignore-deprecated */ Connection::className());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
472 23
                    $relatedTableSchema = $this->db->getTableSchema($relatedTable);
473 23
                    if ($relatedTableSchema !== null) {
474
                        $primaryKeyCount = count($relatedTableSchema->primaryKey);
475
                        if ($primaryKeyCount === 1) {
476
                            $relatedColumn = $relatedTableSchema->primaryKey[0];
477
                        } elseif ($primaryKeyCount > 1) {
478
                            $this->stdout("Related table for field \"{$column}\" exists, but primary key is composite. Default name \"id\" will be used for related field\n", Console::FG_YELLOW);
479
                        } elseif ($primaryKeyCount === 0) {
480 23
                            $this->stdout("Related table for field \"{$column}\" exists, but does not have a primary key. Default name \"id\" will be used for related field.\n", Console::FG_YELLOW);
481
                        }
482
                    }
483
                } catch (\ReflectionException $e) {
484
                    $this->stdout("Cannot initialize database component to try reading referenced table schema for field \"{$column}\". Default name \"id\" will be used for related field.\n", Console::FG_YELLOW);
485
                }
486
            }
487 23
            $foreignKeys[$column] = [
488 23
                'idx' => $this->generateTableName("idx-$table-$column"),
489 23
                'fk' => $this->generateTableName("fk-$table-$column"),
490 23
                'relatedTable' => $this->generateTableName($relatedTable),
491 23
                'relatedColumn' => $relatedColumn,
492 23
            ];
493
        }
494
495 84
        return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [
0 ignored issues
show
Bug introduced by
It seems like Yii::getAlias($templateFile) can also be of type false; however, parameter $file of yii\base\Controller::renderFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

495
        return $this->renderFile(/** @scrutinizer ignore-type */ Yii::getAlias($templateFile), array_merge($params, [
Loading history...
496 84
            'table' => $this->generateTableName($table),
497 84
            'fields' => $fields,
498 84
            'foreignKeys' => $foreignKeys,
499 84
            'tableComment' => $this->comment,
500 84
        ]));
501
    }
502
503
    /**
504
     * If `useTablePrefix` equals true, then the table name will contain the
505
     * prefix format.
506
     *
507
     * @param string $tableName the table name to generate.
508
     * @return string
509
     * @since 2.0.8
510
     */
511 84
    protected function generateTableName($tableName)
512
    {
513 84
        if (!$this->useTablePrefix) {
514
            return $tableName;
515
        }
516
517 84
        return '{{%' . $tableName . '}}';
518
    }
519
520
    /**
521
     * Parse the command line migration fields.
522
     * @return array parse result with following fields:
523
     *
524
     * - fields: array, parsed fields
525
     * - foreignKeys: array, detected foreign keys
526
     *
527
     * @since 2.0.7
528
     */
529 84
    protected function parseFields()
530
    {
531 84
        $fields = [];
532 84
        $foreignKeys = [];
533
534 84
        foreach ($this->fields as $index => $field) {
535 46
            $chunks = $this->splitFieldIntoChunks($field);
536 46
            $property = array_shift($chunks);
0 ignored issues
show
Bug introduced by
It seems like $chunks can also be of type false; however, parameter $array of array_shift() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

536
            $property = array_shift(/** @scrutinizer ignore-type */ $chunks);
Loading history...
537
538 46
            foreach ($chunks as $i => &$chunk) {
539 46
                if (strncmp($chunk, 'foreignKey', 10) === 0) {
540 8
                    preg_match('/foreignKey\((\w*)\s?(\w*)\)/', $chunk, $matches);
541 8
                    $foreignKeys[$property] = [
542 8
                        'table' => isset($matches[1])
543 8
                            ? $matches[1]
544 8
                            : preg_replace('/_id$/', '', $property),
545 8
                        'column' => !empty($matches[2])
546
                            ? $matches[2]
547
                            : null,
548 8
                    ];
549
550 8
                    unset($chunks[$i]);
551 8
                    continue;
552
                }
553
554 46
                if (!preg_match('/^(.+?)\(([^(]+)\)$/', $chunk)) {
555 46
                    $chunk .= '()';
556
                }
557
            }
558 46
            $fields[] = [
559 46
                'property' => $property,
560 46
                'decorators' => implode('->', $chunks),
561 46
            ];
562
        }
563
564 84
        return [
565 84
            'fields' => $fields,
566 84
            'foreignKeys' => $foreignKeys,
567 84
        ];
568
    }
569
570
    /**
571
     * Splits field into chunks
572
     *
573
     * @param string $field
574
     * @return string[]|false
575
     */
576 46
    protected function splitFieldIntoChunks($field)
577
    {
578 46
        $originalDefaultValue = null;
579 46
        $defaultValue = null;
580 46
        preg_match_all('/defaultValue\(["\'].*?:?.*?["\']\)/', $field, $matches, PREG_SET_ORDER, 0);
581 46
        if (isset($matches[0][0])) {
582 5
            $originalDefaultValue = $matches[0][0];
583 5
            $defaultValue = str_replace(':', '{{colon}}', $originalDefaultValue);
584 5
            $field = str_replace($originalDefaultValue, $defaultValue, $field);
585
        }
586
587 46
        $chunks = preg_split('/\s?:\s?/', $field);
588
589 46
        if (is_array($chunks) && $defaultValue !== null && $originalDefaultValue !== null) {
590 5
            foreach ($chunks as $key => $chunk) {
591 5
                $chunks[$key] = str_replace($defaultValue, $originalDefaultValue, $chunk);
592
            }
593
        }
594
595 46
        return $chunks;
596
    }
597
598
    /**
599
     * Adds default primary key to fields list if there's no primary key specified.
600
     * @param array $fields parsed fields
601
     * @since 2.0.7
602
     */
603 39
    protected function addDefaultPrimaryKey(&$fields)
604
    {
605 39
        foreach ($fields as $field) {
606 20
            if ($field['property'] === 'id' || false !== strripos($field['decorators'], 'primarykey()')) {
607 10
                return;
608
            }
609
        }
610 29
        array_unshift($fields, ['property' => 'id', 'decorators' => 'primaryKey()']);
611
    }
612
}
613