GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( e69234...7a3a34 )
by Robert
17:20
created

MigrateController::parseFields()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 29
cts 29
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 26
nc 8
nop 0
crap 7
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
17
/**
18
 * Manages application migrations.
19
 *
20
 * A migration means a set of persistent changes to the application environment
21
 * that is shared among different developers. For example, in an application
22
 * backed by a database, a migration may refer to a set of changes to
23
 * the database, such as creating a new table, adding a new table column.
24
 *
25
 * This command provides support for tracking the migration history, upgrading
26
 * or downloading with migrations, and creating new migration skeletons.
27
 *
28
 * The migration history is stored in a database table named
29
 * as [[migrationTable]]. The table will be automatically created the first time
30
 * this command is executed, if it does not exist. You may also manually
31
 * create it as follows:
32
 *
33
 * ```sql
34
 * CREATE TABLE migration (
35
 *     version varchar(180) PRIMARY KEY,
36
 *     apply_time integer
37
 * )
38
 * ```
39
 *
40
 * Below are some common usages of this command:
41
 *
42
 * ```
43
 * # creates a new migration named 'create_user_table'
44
 * yii migrate/create create_user_table
45
 *
46
 * # applies ALL new migrations
47
 * yii migrate
48
 *
49
 * # reverts the last applied migration
50
 * yii migrate/down
51
 * ```
52
 *
53
 * Since 2.0.10 you can use namespaced migrations. In order to enable this feature you should configure [[migrationNamespaces]]
54
 * property for the controller at application configuration:
55
 *
56
 * ```php
57
 * return [
58
 *     'controllerMap' => [
59
 *         'migrate' => [
60
 *             'class' => 'yii\console\controllers\MigrateController',
61
 *             'migrationNamespaces' => [
62
 *                 'app\migrations',
63
 *                 'some\extension\migrations',
64
 *             ],
65
 *             //'migrationPath' => null, // allows to disable not namespaced migration completely
66
 *         ],
67
 *     ],
68
 * ];
69
 * ```
70
 *
71
 * @author Qiang Xue <[email protected]>
72
 * @since 2.0
73
 */
74
class MigrateController extends BaseMigrateController
75
{
76
    /**
77
     * @var string the name of the table for keeping applied migration information.
78
     */
79
    public $migrationTable = '{{%migration}}';
80
    /**
81
     * @inheritdoc
82
     */
83
    public $templateFile = '@yii/views/migration.php';
84
    /**
85
     * @var array a set of template paths for generating migration code automatically.
86
     *
87
     * The key is the template type, the value is a path or the alias. Supported types are:
88
     * - `create_table`: table creating template
89
     * - `drop_table`: table dropping template
90
     * - `add_column`: adding new column template
91
     * - `drop_column`: dropping column template
92
     * - `create_junction`: create junction template
93
     *
94
     * @since 2.0.7
95
     */
96
    public $generatorTemplateFiles = [
97
        'create_table' => '@yii/views/createTableMigration.php',
98
        'drop_table' => '@yii/views/dropTableMigration.php',
99
        'add_column' => '@yii/views/addColumnMigration.php',
100
        'drop_column' => '@yii/views/dropColumnMigration.php',
101
        'create_junction' => '@yii/views/createTableMigration.php',
102
    ];
103
    /**
104
     * @var bool indicates whether the table names generated should consider
105
     * the `tablePrefix` setting of the DB connection. For example, if the table
106
     * name is `post` the generator wil return `{{%post}}`.
107
     * @since 2.0.8
108
     */
109
    public $useTablePrefix = false;
110
    /**
111
     * @var array column definition strings used for creating migration code.
112
     *
113
     * The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Delimiter is `,`.
114
     * For example, `--fields="name:string(12):notNull:unique"`
115
     * produces a string column of size 12 which is not null and unique values.
116
     *
117
     * Note: primary key is added automatically and is named id by default.
118
     * If you want to use another name you may specify it explicitly like
119
     * `--fields="id_key:primaryKey,name:string(12):notNull:unique"`
120
     * @since 2.0.7
121
     */
122
    public $fields = [];
123
    /**
124
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection to use
125
     * when applying migrations. Starting from version 2.0.3, this can also be a configuration array
126
     * for creating the object.
127
     */
128
    public $db = 'db';
129
130
131
    /**
132
     * @inheritdoc
133
     */
134 16
    public function options($actionID)
135
    {
136 16
        return array_merge(
137 16
            parent::options($actionID),
138 16
            ['migrationTable', 'db'], // global for all actions
139
            $actionID === 'create'
140 16
                ? ['templateFile', 'fields', 'useTablePrefix']
141 16
                : []
142 16
        );
143
    }
144
145
    /**
146
     * @inheritdoc
147
     * @since 2.0.8
148
     */
149
    public function optionAliases()
150
    {
151
        return array_merge(parent::optionAliases(), [
152
            'f' => 'fields',
153
            'p' => 'migrationPath',
154
            't' => 'migrationTable',
155
            'F' => 'templateFile',
156
            'P' => 'useTablePrefix',
157
        ]);
158
    }
159
160
    /**
161
     * This method is invoked right before an action is to be executed (after all possible filters.)
162
     * It checks the existence of the [[migrationPath]].
163
     * @param \yii\base\Action $action the action to be executed.
164
     * @return bool whether the action should continue to be executed.
165
     */
166 23
    public function beforeAction($action)
167
    {
168 23
        if (parent::beforeAction($action)) {
169 23
            if ($action->id !== 'create') {
170 15
                $this->db = Instance::ensure($this->db, Connection::className());
171 15
            }
172 23
            return true;
173
        } else {
174
            return false;
175
        }
176
    }
177
178
    /**
179
     * Creates a new migration instance.
180
     * @param string $class the migration class name
181
     * @return \yii\db\Migration the migration instance
182
     */
183 13
    protected function createMigration($class)
184
    {
185 13
        $class = trim($class, '\\');
186 13
        if (strpos($class, '\\') === false) {
187 9
            $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
188 9
            require_once($file);
189 9
        }
190
191 13
        return new $class(['db' => $this->db]);
192
    }
193
194
    /**
195
     * @inheritdoc
196
     */
197 15
    protected function getMigrationHistory($limit)
198
    {
199 15
        if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
200 15
            $this->createMigrationHistoryTable();
201 15
        }
202 15
        $query = new Query;
203 15
        $rows = $query->select(['version', 'apply_time'])
204 15
            ->from($this->migrationTable)
205 15
            ->orderBy('apply_time DESC, version DESC')
206 15
            ->limit($limit)
207 15
            ->createCommand($this->db)
208 15
            ->queryAll();
209 15
        $history = [];
210 15
        foreach ($rows as $row) {
211 15
            if ($row['version'] === self::BASE_MIGRATION) {
212 15
                continue;
213
            }
214 8
            if (preg_match('/m?(\d{6}_?\d{6})(\D.*)?$/is', $row['version'], $matches)) {
215 8
                $time = str_replace('_', '', $matches[1]);
216 8
                $history['m' . $time . $matches[2]] = $row;
217 8
            }
218 15
        }
219
        // sort history in desc order
220 15
        uksort($history, function ($a, $b) {
221 3
            return strcasecmp($b, $a);
222 15
        });
223 15
        $history = ArrayHelper::map($history, 'version', 'apply_time');
224
225 15
        return $history;
226
    }
227
228
    /**
229
     * Creates the migration history table.
230
     */
231 15
    protected function createMigrationHistoryTable()
232
    {
233 15
        $tableName = $this->db->schema->getRawTableName($this->migrationTable);
234 15
        $this->stdout("Creating migration history table \"$tableName\"...", Console::FG_YELLOW);
235 15
        $this->db->createCommand()->createTable($this->migrationTable, [
236 15
            'version' => 'varchar(180) NOT NULL PRIMARY KEY',
237 15
            'apply_time' => 'integer',
238 15
        ])->execute();
239 15
        $this->db->createCommand()->insert($this->migrationTable, [
240 15
            'version' => self::BASE_MIGRATION,
241 15
            'apply_time' => time(),
242 15
        ])->execute();
243 15
        $this->stdout("Done.\n", Console::FG_GREEN);
244 15
    }
245
246
    /**
247
     * @inheritdoc
248
     */
249 15
    protected function addMigrationHistory($version)
250
    {
251 15
        $command = $this->db->createCommand();
252 15
        $command->insert($this->migrationTable, [
253 15
            'version' => $version,
254 15
            'apply_time' => time(),
255 15
        ])->execute();
256 15
    }
257
258
    /**
259
     * @inheritdoc
260
     */
261 5
    protected function removeMigrationHistory($version)
262
    {
263 5
        $command = $this->db->createCommand();
264 5
        $command->delete($this->migrationTable, [
265 5
            'version' => $version,
266 5
        ])->execute();
267 5
    }
268
269
    /**
270
     * @inheritdoc
271
     * @since 2.0.8
272
     */
273 8
    protected function generateMigrationSourceCode($params)
274
    {
275 8
        $parsedFields = $this->parseFields();
276 8
        $fields = $parsedFields['fields'];
277 8
        $foreignKeys = $parsedFields['foreignKeys'];
278
279 8
        $name = $params['name'];
280
281 8
        $templateFile = $this->templateFile;
282 8
        $table = null;
283 8
        if (preg_match('/^create_junction(?:_table_for_|_for_|_)(.+)_and_(.+)_tables?$/', $name, $matches)) {
284 1
            $templateFile = $this->generatorTemplateFiles['create_junction'];
285 1
            $firstTable = $matches[1];
286 1
            $secondTable = $matches[2];
287
288 1
            $fields = array_merge(
289
                [
290
                    [
291 1
                        'property' => $firstTable . '_id',
292 1
                        'decorators' => 'integer()',
293 1
                    ],
294
                    [
295 1
                        'property' => $secondTable . '_id',
296 1
                        'decorators' => 'integer()',
297 1
                    ],
298 1
                ],
299 1
                $fields,
300
                [
301
                    [
302
                        'property' => 'PRIMARY KEY(' .
303 1
                            $firstTable . '_id, ' .
304 1
                            $secondTable . '_id)',
305 1
                    ],
306
                ]
307 1
            );
308
309 1
            $foreignKeys[$firstTable . '_id']['table'] = $firstTable;
310 1
            $foreignKeys[$secondTable . '_id']['table'] = $secondTable;
311 1
            $foreignKeys[$firstTable . '_id']['column'] = null;
312 1
            $foreignKeys[$secondTable . '_id']['column'] = null;
313 1
            $table = $firstTable . '_' . $secondTable;
314 8
        } elseif (preg_match('/^add_(.+)_columns?_to_(.+)_table$/', $name, $matches)) {
315 1
            $templateFile = $this->generatorTemplateFiles['add_column'];
316 1
            $table = $matches[2];
317 7
        } elseif (preg_match('/^drop_(.+)_columns?_from_(.+)_table$/', $name, $matches)) {
318 1
            $templateFile = $this->generatorTemplateFiles['drop_column'];
319 1
            $table = $matches[2];
320 6
        } elseif (preg_match('/^create_(.+)_table$/', $name, $matches)) {
321 1
            $this->addDefaultPrimaryKey($fields);
322 1
            $templateFile = $this->generatorTemplateFiles['create_table'];
323 1
            $table = $matches[1];
324 5
        } elseif (preg_match('/^drop_(.+)_table$/', $name, $matches)) {
325 2
            $this->addDefaultPrimaryKey($fields);
326 2
            $templateFile = $this->generatorTemplateFiles['drop_table'];
327 2
            $table = $matches[1];
328 2
        }
329
330 8
        foreach ($foreignKeys as $column => $foreignKey) {
331 3
            $relatedColumn = $foreignKey['column'];
332 3
            $relatedTable = $foreignKey['table'];
333
            // Since 2.0.11 if related column name is not specified,
334
            // we're trying to get it from table schema
335
            // @see https://github.com/yiisoft/yii2/issues/12748
336 3
            if ($relatedColumn === null) {
337 3
                $relatedColumn = 'id';
338
                try {
339 3
                    $this->db = Instance::ensure($this->db, Connection::className());
340 3
                    $relatedTableSchema = $this->db->getTableSchema($relatedTable);
341 3
                    if ($relatedTableSchema !== null) {
342
                        $primaryKeyCount = count($relatedTableSchema->primaryKey);
343
                        if ($primaryKeyCount === 1) {
344
                            $relatedColumn = $relatedTableSchema->primaryKey[0];
345
                        } elseif ($primaryKeyCount > 1) {
346
                            $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);
347
                        } elseif ($primaryKeyCount === 0) {
348
                            $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);
349
                        }
350
                    }
351 3
                } catch (\ReflectionException $e) {
352
                    $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);
353
                }
354 3
            }
355 3
            $foreignKeys[$column] = [
356 3
                'idx' => $this->generateTableName("idx-$table-$column"),
357 3
                'fk' => $this->generateTableName("fk-$table-$column"),
358 3
                'relatedTable' => $this->generateTableName($relatedTable),
359 3
                'relatedColumn' => $relatedColumn,
360
            ];
361 8
        }
362
363 8
        return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [
0 ignored issues
show
Bug introduced by
It seems like \Yii::getAlias($templateFile) targeting yii\BaseYii::getAlias() can also be of type boolean; however, yii\base\Controller::renderFile() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
364 8
            'table' => $this->generateTableName($table),
365 8
            'fields' => $fields,
366 8
            'foreignKeys' => $foreignKeys,
367 8
        ]));
368
    }
369
370
    /**
371
     * If `useTablePrefix` equals true, then the table name will contain the
372
     * prefix format.
373
     *
374
     * @param string $tableName the table name to generate.
375
     * @return string
376
     * @since 2.0.8
377
     */
378 8
    protected function generateTableName($tableName)
379
    {
380 8
        if (!$this->useTablePrefix) {
381 8
            return $tableName;
382
        }
383 2
        return '{{%' . $tableName . '}}';
384
    }
385
386
    /**
387
     * Parse the command line migration fields
388
     * @return array parse result with following fields:
389
     *
390
     * - fields: array, parsed fields
391
     * - foreignKeys: array, detected foreign keys
392
     *
393
     * @since 2.0.7
394
     */
395 8
    protected function parseFields()
396
    {
397 8
        $fields = [];
398 8
        $foreignKeys = [];
399
400 8
        foreach ($this->fields as $index => $field) {
401 4
            $chunks = preg_split('/\s?:\s?/', $field, null);
402 4
            $property = array_shift($chunks);
403
404 4
            foreach ($chunks as $i => &$chunk) {
405 4
                if (strpos($chunk, 'foreignKey') === 0) {
406 2
                    preg_match('/foreignKey\((\w*)\s?(\w*)\)/', $chunk, $matches);
407 2
                    $foreignKeys[$property] = [
408 2
                        'table' => isset($matches[1])
409 2
                            ? $matches[1]
410 2
                            : preg_replace('/_id$/', '', $property),
411 2
                        'column' => !empty($matches[2])
412 2
                            ? $matches[2]
413 2
                            : null,
414
                    ];
415
416 2
                    unset($chunks[$i]);
417 2
                    continue;
418
                }
419
420 4
                if (!preg_match('/^(.+?)\(([^(]+)\)$/', $chunk)) {
421 4
                    $chunk .= '()';
422 4
                }
423 4
            }
424 4
            $fields[] = [
425 4
                'property' => $property,
426 4
                'decorators' => implode('->', $chunks),
427
            ];
428 8
        }
429
430
        return [
431 8
            'fields' => $fields,
432 8
            'foreignKeys' => $foreignKeys,
433 8
        ];
434
    }
435
436
    /**
437
     * Adds default primary key to fields list if there's no primary key specified
438
     * @param array $fields parsed fields
439
     * @since 2.0.7
440
     */
441 2
    protected function addDefaultPrimaryKey(&$fields)
442
    {
443 2
        foreach ($fields as $field) {
444 2
            if ($field['decorators'] === 'primaryKey()' || $field['decorators'] === 'bigPrimaryKey()') {
445 1
                return;
446
            }
447 2
        }
448 2
        array_unshift($fields, ['property' => 'id', 'decorators' => 'primaryKey()']);
449 2
    }
450
}
451