Passed
Push — fix-php-74 ( f6eb65...a1ad6b )
by Alexander
38:08 queued 12:19
created

MigrateController::createMigration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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

183
            $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...
184 141
            return true;
185
        }
186
187
        return false;
188
    }
189
190
    /**
191
     * Creates a new migration instance.
192
     * @param string $class the migration class name
193
     * @return \yii\db\Migration the migration instance
194
     */
195 53
    protected function createMigration($class)
196
    {
197 53
        $this->includeMigrationFile($class);
198
199 53
        return Yii::createObject([
200 53
            'class' => $class,
201 53
            'db' => $this->db,
202 53
            'compact' => $this->compact,
203
        ]);
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209 59
    protected function getMigrationHistory($limit)
210
    {
211 59
        if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
212 28
            $this->createMigrationHistoryTable();
213
        }
214 59
        $query = (new Query())
215 59
            ->select(['version', 'apply_time'])
216 59
            ->from($this->migrationTable)
217 59
            ->orderBy(['apply_time' => SORT_DESC, 'version' => SORT_DESC]);
218
219 59
        if (empty($this->migrationNamespaces)) {
220 52
            $query->limit($limit);
221 52
            $rows = $query->all($this->db);
222 52
            $history = ArrayHelper::map($rows, 'version', 'apply_time');
223 52
            unset($history[self::BASE_MIGRATION]);
224 52
            return $history;
225
        }
226
227 7
        $rows = $query->all($this->db);
228
229 7
        $history = [];
230 7
        foreach ($rows as $key => $row) {
231 7
            if ($row['version'] === self::BASE_MIGRATION) {
232 7
                continue;
233
            }
234 4
            if (preg_match('/m?(\d{6}_?\d{6})(\D.*)?$/is', $row['version'], $matches)) {
235 4
                $time = str_replace('_', '', $matches[1]);
236 4
                $row['canonicalVersion'] = $time;
237
            } else {
238
                $row['canonicalVersion'] = $row['version'];
239
            }
240 4
            $row['apply_time'] = (int) $row['apply_time'];
241 4
            $history[] = $row;
242
        }
243
244 7
        usort($history, function ($a, $b) {
245 4
            if ($a['apply_time'] === $b['apply_time']) {
246 4
                if (($compareResult = strcasecmp($b['canonicalVersion'], $a['canonicalVersion'])) !== 0) {
247 2
                    return $compareResult;
248
                }
249
250 2
                return strcasecmp($b['version'], $a['version']);
251
            }
252
253 1
            return ($a['apply_time'] > $b['apply_time']) ? -1 : +1;
254 7
        });
255
256 7
        $history = array_slice($history, 0, $limit);
257
258 7
        $history = ArrayHelper::map($history, 'version', 'apply_time');
259
260 7
        return $history;
261
    }
262
263
    /**
264
     * Creates the migration history table.
265
     */
266 28
    protected function createMigrationHistoryTable()
267
    {
268 28
        $tableName = $this->db->schema->getRawTableName($this->migrationTable);
269 28
        $this->stdout("Creating migration history table \"$tableName\"...", Console::FG_YELLOW);
270 28
        $this->db->createCommand()->createTable($this->migrationTable, [
271 28
            'version' => 'varchar(' . static::MAX_NAME_LENGTH . ') NOT NULL PRIMARY KEY',
272 28
            'apply_time' => 'integer',
273 28
        ])->execute();
274 28
        $this->db->createCommand()->insert($this->migrationTable, [
275 28
            'version' => self::BASE_MIGRATION,
276 28
            'apply_time' => time(),
277 28
        ])->execute();
278 28
        $this->stdout("Done.\n", Console::FG_GREEN);
279 28
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 55
    protected function addMigrationHistory($version)
285
    {
286 55
        $command = $this->db->createCommand();
287 55
        $command->insert($this->migrationTable, [
288 55
            'version' => $version,
289 55
            'apply_time' => time(),
290 55
        ])->execute();
291 55
    }
292
293
    /**
294
     * {@inheritdoc}
295
     * @since 2.0.13
296
     */
297 2
    protected function truncateDatabase()
298
    {
299 2
        $db = $this->db;
300 2
        $schemas = $db->schema->getTableSchemas();
301
302
        // First drop all foreign keys,
303 2
        foreach ($schemas as $schema) {
304 2
            if ($schema->foreignKeys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $schema->foreignKeys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
305 1
                foreach ($schema->foreignKeys as $name => $foreignKey) {
306 1
                    $db->createCommand()->dropForeignKey($name, $schema->name)->execute();
307 2
                    $this->stdout("Foreign key $name dropped.\n");
308
                }
309
            }
310
        }
311
312
        // Then drop the tables:
313 2
        foreach ($schemas as $schema) {
314
            try {
315 2
                $db->createCommand()->dropTable($schema->name)->execute();
316 2
                $this->stdout("Table {$schema->name} dropped.\n");
317 2
            } catch (\Exception $e) {
318 2
                if ($this->isViewRelated($e->getMessage())) {
319 2
                    $db->createCommand()->dropView($schema->name)->execute();
320 2
                    $this->stdout("View {$schema->name} dropped.\n");
321
                } else {
322 2
                    $this->stdout("Cannot drop {$schema->name} Table .\n");
323
                }
324
            }
325
        }
326 2
    }
327
328
    /**
329
     * Determines whether the error message is related to deleting a view or not
330
     * @param string $errorMessage
331
     * @return bool
332
     */
333 2
    private function isViewRelated($errorMessage)
334
    {
335
        $dropViewErrors = [
336 2
            'DROP VIEW to delete view', // SQLite
337
            'SQLSTATE[42S02]', // MySQL
338
        ];
339
340 2
        foreach ($dropViewErrors as $dropViewError) {
341 2
            if (strpos($errorMessage, $dropViewError) !== false) {
342 2
                return true;
343
            }
344
        }
345
346
        return false;
347
    }
348
349
    /**
350
     * {@inheritdoc}
351
     */
352 44
    protected function removeMigrationHistory($version)
353
    {
354 44
        $command = $this->db->createCommand();
355 44
        $command->delete($this->migrationTable, [
356 44
            'version' => $version,
357 44
        ])->execute();
358 44
    }
359
360
    private $_migrationNameLimit;
361
362
    /**
363
     * {@inheritdoc}
364
     * @since 2.0.13
365
     */
366 136
    protected function getMigrationNameLimit()
367
    {
368 136
        if ($this->_migrationNameLimit !== null) {
369 8
            return $this->_migrationNameLimit;
370
        }
371 136
        $tableSchema = $this->db->schema ? $this->db->schema->getTableSchema($this->migrationTable, true) : null;
372 136
        if ($tableSchema !== null) {
373 54
            return $this->_migrationNameLimit = $tableSchema->columns['version']->size;
374
        }
375
376 82
        return static::MAX_NAME_LENGTH;
377
    }
378
379
    /**
380
     * Normalizes table name for generator.
381
     * When name is preceded with underscore name case is kept - otherwise it's converted from camelcase to underscored.
382
     * Last underscore is always trimmed so if there should be underscore at the end of name use two of them.
383
     * @param string $name
384
     * @return string
385
     */
386 78
    private function normalizeTableName($name)
387
    {
388 78
        if (substr($name, -1) === '_') {
389 58
            $name = substr($name, 0, -1);
390
        }
391
392 78
        if (strpos($name, '_') === 0) {
393 54
            return substr($name, 1);
394
        }
395
396 26
        return Inflector::underscore($name);
397
    }
398
399
    /**
400
     * {@inheritdoc}
401
     * @since 2.0.8
402
     */
403 82
    protected function generateMigrationSourceCode($params)
404
    {
405 82
        $parsedFields = $this->parseFields();
406 82
        $fields = $parsedFields['fields'];
407 82
        $foreignKeys = $parsedFields['foreignKeys'];
408
409 82
        $name = $params['name'];
410 82
        if ($params['namespace']) {
411 80
            $name = substr($name, strrpos($name, '\\') + 1);
412
        }
413
414 82
        $templateFile = $this->templateFile;
415 82
        $table = null;
416 82
        if (preg_match(
417 82
            '/^create_?junction_?(?:table)?_?(?:for)?(.+)_?and(.+)_?tables?$/i',
418 82
            $name,
419 82
            $matches
420
        )) {
421 15
            $templateFile = $this->generatorTemplateFiles['create_junction'];
422 15
            $firstTable = $this->normalizeTableName($matches[1]);
423 15
            $secondTable = $this->normalizeTableName($matches[2]);
424
425 15
            $fields = array_merge(
426
                [
427
                    [
428 15
                        'property' => $firstTable . '_id',
429 15
                        'decorators' => 'integer()',
430
                    ],
431
                    [
432 15
                        'property' => $secondTable . '_id',
433 15
                        'decorators' => 'integer()',
434
                    ],
435
                ],
436 15
                $fields,
437
                [
438
                    [
439
                        'property' => 'PRIMARY KEY(' .
440 15
                            $firstTable . '_id, ' .
441 15
                            $secondTable . '_id)',
442
                    ],
443
                ]
444
            );
445
446 15
            $foreignKeys[$firstTable . '_id']['table'] = $firstTable;
447 15
            $foreignKeys[$secondTable . '_id']['table'] = $secondTable;
448 15
            $foreignKeys[$firstTable . '_id']['column'] = null;
449 15
            $foreignKeys[$secondTable . '_id']['column'] = null;
450 15
            $table = $firstTable . '_' . $secondTable;
451 67
        } elseif (preg_match('/^add(.+)columns?_?to(.+)table$/i', $name, $matches)) {
452 15
            $templateFile = $this->generatorTemplateFiles['add_column'];
453 15
            $table = $this->normalizeTableName($matches[2]);
454 52
        } elseif (preg_match('/^drop(.+)columns?_?from(.+)table$/i', $name, $matches)) {
455 11
            $templateFile = $this->generatorTemplateFiles['drop_column'];
456 11
            $table = $this->normalizeTableName($matches[2]);
457 41
        } elseif (preg_match('/^create(.+)table$/i', $name, $matches)) {
458 25
            $this->addDefaultPrimaryKey($fields);
459 25
            $templateFile = $this->generatorTemplateFiles['create_table'];
460 25
            $table = $this->normalizeTableName($matches[1]);
461 16
        } elseif (preg_match('/^drop(.+)table$/i', $name, $matches)) {
462 12
            $this->addDefaultPrimaryKey($fields);
463 12
            $templateFile = $this->generatorTemplateFiles['drop_table'];
464 12
            $table = $this->normalizeTableName($matches[1]);
465
        }
466
467 82
        foreach ($foreignKeys as $column => $foreignKey) {
468 23
            $relatedColumn = $foreignKey['column'];
469 23
            $relatedTable = $foreignKey['table'];
470
            // Since 2.0.11 if related column name is not specified,
471
            // we're trying to get it from table schema
472
            // @see https://github.com/yiisoft/yii2/issues/12748
473 23
            if ($relatedColumn === null) {
474 23
                $relatedColumn = 'id';
475
                try {
476 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

476
                    $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...
477 23
                    $relatedTableSchema = $this->db->getTableSchema($relatedTable);
478 23
                    if ($relatedTableSchema !== null) {
479
                        $primaryKeyCount = count($relatedTableSchema->primaryKey);
480
                        if ($primaryKeyCount === 1) {
481
                            $relatedColumn = $relatedTableSchema->primaryKey[0];
482
                        } elseif ($primaryKeyCount > 1) {
483
                            $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);
484
                        } elseif ($primaryKeyCount === 0) {
485 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);
486
                        }
487
                    }
488
                } catch (\ReflectionException $e) {
489
                    $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);
490
                }
491
            }
492 23
            $foreignKeys[$column] = [
493 23
                'idx' => $this->generateTableName("idx-$table-$column"),
494 23
                'fk' => $this->generateTableName("fk-$table-$column"),
495 23
                'relatedTable' => $this->generateTableName($relatedTable),
496 23
                'relatedColumn' => $relatedColumn,
497
            ];
498
        }
499
500 82
        return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [
501 82
            'table' => $this->generateTableName($table),
502 82
            'fields' => $fields,
503 82
            'foreignKeys' => $foreignKeys,
504 82
            'tableComment' => $this->comment,
505
        ]));
506
    }
507
508
    /**
509
     * If `useTablePrefix` equals true, then the table name will contain the
510
     * prefix format.
511
     *
512
     * @param string $tableName the table name to generate.
513
     * @return string
514
     * @since 2.0.8
515
     */
516 82
    protected function generateTableName($tableName)
517
    {
518 82
        if (!$this->useTablePrefix) {
519
            return $tableName;
520
        }
521
522 82
        return '{{%' . $tableName . '}}';
523
    }
524
525
    /**
526
     * Parse the command line migration fields.
527
     * @return array parse result with following fields:
528
     *
529
     * - fields: array, parsed fields
530
     * - foreignKeys: array, detected foreign keys
531
     *
532
     * @since 2.0.7
533
     */
534 82
    protected function parseFields()
535
    {
536 82
        $fields = [];
537 82
        $foreignKeys = [];
538
539 82
        foreach ($this->fields as $index => $field) {
540 44
            $chunks = $this->splitFieldIntoChunks($field);
541 44
            $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

541
            $property = array_shift(/** @scrutinizer ignore-type */ $chunks);
Loading history...
542
543 44
            foreach ($chunks as $i => &$chunk) {
544 44
                if (strncmp($chunk, 'foreignKey', 10) === 0) {
545 8
                    preg_match('/foreignKey\((\w*)\s?(\w*)\)/', $chunk, $matches);
546 8
                    $foreignKeys[$property] = [
547 8
                        'table' => isset($matches[1])
548 8
                            ? $matches[1]
549 8
                            : preg_replace('/_id$/', '', $property),
550 8
                        'column' => !empty($matches[2])
551
                            ? $matches[2]
552
                            : null,
553
                    ];
554
555 8
                    unset($chunks[$i]);
556 8
                    continue;
557
                }
558
559 44
                if (!preg_match('/^(.+?)\(([^(]+)\)$/', $chunk)) {
560 44
                    $chunk .= '()';
561
                }
562
            }
563 44
            $fields[] = [
564 44
                'property' => $property,
565 44
                'decorators' => implode('->', $chunks),
566
            ];
567
        }
568
569
        return [
570 82
            'fields' => $fields,
571 82
            'foreignKeys' => $foreignKeys,
572
        ];
573
    }
574
575
    /**
576
     * Splits field into chunks
577
     *
578
     * @param string $field
579
     * @return string[]|false
580
     */
581 44
    protected function splitFieldIntoChunks($field)
582
    {
583 44
        $hasDoubleQuotes = false;
584 44
        preg_match_all('/defaultValue\(.*?:.*?\)/', $field, $matches);
585 44
        if (isset($matches[0][0])) {
586 1
            $hasDoubleQuotes = true;
587 1
            $originalDefaultValue = $matches[0][0];
588 1
            $defaultValue = str_replace(':', '{{colon}}', $originalDefaultValue);
589 1
            $field = str_replace($originalDefaultValue, $defaultValue, $field);
590
        }
591
592 44
        $chunks = preg_split('/\s?:\s?/', $field);
593
594 44
        if (is_array($chunks) && $hasDoubleQuotes) {
595 1
            foreach ($chunks as $key => $chunk) {
596 1
                $chunks[$key] = str_replace($defaultValue, $originalDefaultValue, $chunk);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $originalDefaultValue does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $defaultValue does not seem to be defined for all execution paths leading up to this point.
Loading history...
597
            }
598
        }
599
600 44
        return $chunks;
601
    }
602
603
    /**
604
     * Adds default primary key to fields list if there's no primary key specified.
605
     * @param array $fields parsed fields
606
     * @since 2.0.7
607
     */
608 37
    protected function addDefaultPrimaryKey(&$fields)
609
    {
610 37
        foreach ($fields as $field) {
611 18
            if (false !== strripos($field['decorators'], 'primarykey()')) {
612 18
                return;
613
            }
614
        }
615 29
        array_unshift($fields, ['property' => 'id', 'decorators' => 'primaryKey()']);
616 29
    }
617
}
618