1 | <?php |
||||
2 | /** |
||||
3 | * @link https://www.yiiframework.com/ |
||||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||||
5 | * @license https://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 | 85 | ? ['templateFile', 'fields', 'useTablePrefix', 'comment'] |
|||
153 | 130 | : [] |
|||
154 | 130 | ); |
|||
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()); |
|||
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 | 51 | protected function createMigration($class) |
|||
196 | { |
||||
197 | 51 | $this->includeMigrationFile($class); |
|||
198 | |||||
199 | 51 | return Yii::createObject([ |
|||
200 | 51 | 'class' => $class, |
|||
201 | 51 | 'db' => $this->db, |
|||
202 | 51 | 'compact' => $this->compact, |
|||
203 | 51 | ]); |
|||
204 | } |
||||
205 | |||||
206 | /** |
||||
207 | * {@inheritdoc} |
||||
208 | */ |
||||
209 | 57 | protected function getMigrationHistory($limit) |
|||
210 | { |
||||
211 | 57 | if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) { |
|||
212 | 30 | $this->createMigrationHistoryTable(); |
|||
213 | } |
||||
214 | 57 | $query = (new Query()) |
|||
215 | 57 | ->select(['version', 'apply_time']) |
|||
216 | 57 | ->from($this->migrationTable) |
|||
217 | 57 | ->orderBy(['apply_time' => SORT_DESC, 'version' => SORT_DESC]); |
|||
218 | |||||
219 | 57 | if (empty($this->migrationNamespaces)) { |
|||
220 | 50 | $query->limit($limit); |
|||
221 | 50 | $rows = $query->all($this->db); |
|||
222 | 50 | $history = ArrayHelper::map($rows, 'version', 'apply_time'); |
|||
223 | 50 | unset($history[self::BASE_MIGRATION]); |
|||
224 | 50 | 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 | 30 | protected function createMigrationHistoryTable() |
|||
267 | { |
||||
268 | 30 | $tableName = $this->db->schema->getRawTableName($this->migrationTable); |
|||
269 | 30 | $this->stdout("Creating migration history table \"$tableName\"...", Console::FG_YELLOW); |
|||
270 | 30 | $this->db->createCommand()->createTable($this->migrationTable, [ |
|||
271 | 30 | 'version' => 'varchar(' . static::MAX_NAME_LENGTH . ') NOT NULL PRIMARY KEY', |
|||
272 | 30 | 'apply_time' => 'integer', |
|||
273 | 30 | ])->execute(); |
|||
274 | 30 | $this->db->createCommand()->insert($this->migrationTable, [ |
|||
275 | 30 | 'version' => self::BASE_MIGRATION, |
|||
276 | 30 | 'apply_time' => time(), |
|||
277 | 30 | ])->execute(); |
|||
278 | 30 | $this->stdout("Done.\n", Console::FG_GREEN); |
|||
279 | } |
||||
280 | |||||
281 | /** |
||||
282 | * {@inheritdoc} |
||||
283 | */ |
||||
284 | 53 | protected function addMigrationHistory($version) |
|||
285 | { |
||||
286 | 53 | $command = $this->db->createCommand(); |
|||
287 | 53 | $command->insert($this->migrationTable, [ |
|||
288 | 53 | 'version' => $version, |
|||
289 | 53 | 'apply_time' => time(), |
|||
290 | 53 | ])->execute(); |
|||
291 | } |
||||
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 | foreach ($schema->foreignKeys as $name => $foreignKey) { |
|||
305 | 1 | $db->createCommand()->dropForeignKey($name, $schema->name)->execute(); |
|||
306 | 1 | $this->stdout("Foreign key $name dropped.\n"); |
|||
307 | } |
||||
308 | } |
||||
309 | |||||
310 | // Then drop the tables: |
||||
311 | 2 | foreach ($schemas as $schema) { |
|||
312 | try { |
||||
313 | 2 | $db->createCommand()->dropTable($schema->name)->execute(); |
|||
314 | 2 | $this->stdout("Table {$schema->name} dropped.\n"); |
|||
315 | 2 | } catch (\Exception $e) { |
|||
316 | 2 | if ($this->isViewRelated($e->getMessage())) { |
|||
317 | 2 | $db->createCommand()->dropView($schema->name)->execute(); |
|||
318 | 2 | $this->stdout("View {$schema->name} dropped.\n"); |
|||
319 | } else { |
||||
320 | $this->stdout("Cannot drop {$schema->name} Table .\n"); |
||||
321 | } |
||||
322 | } |
||||
323 | } |
||||
324 | } |
||||
325 | |||||
326 | /** |
||||
327 | * Determines whether the error message is related to deleting a view or not |
||||
328 | * @param string $errorMessage |
||||
329 | * @return bool |
||||
330 | */ |
||||
331 | 2 | private function isViewRelated($errorMessage) |
|||
332 | { |
||||
333 | 2 | $dropViewErrors = [ |
|||
334 | 2 | 'DROP VIEW to delete view', // SQLite |
|||
335 | 2 | 'SQLSTATE[42S02]', // MySQL |
|||
336 | 2 | ]; |
|||
337 | |||||
338 | 2 | foreach ($dropViewErrors as $dropViewError) { |
|||
339 | 2 | if (strpos($errorMessage, $dropViewError) !== false) { |
|||
340 | 2 | return true; |
|||
341 | } |
||||
342 | } |
||||
343 | |||||
344 | return false; |
||||
345 | } |
||||
346 | |||||
347 | /** |
||||
348 | * {@inheritdoc} |
||||
349 | */ |
||||
350 | 42 | protected function removeMigrationHistory($version) |
|||
351 | { |
||||
352 | 42 | $command = $this->db->createCommand(); |
|||
353 | 42 | $command->delete($this->migrationTable, [ |
|||
354 | 42 | 'version' => $version, |
|||
355 | 42 | ])->execute(); |
|||
356 | } |
||||
357 | |||||
358 | private $_migrationNameLimit; |
||||
359 | |||||
360 | /** |
||||
361 | * {@inheritdoc} |
||||
362 | * @since 2.0.13 |
||||
363 | */ |
||||
364 | 136 | protected function getMigrationNameLimit() |
|||
365 | { |
||||
366 | 136 | if ($this->_migrationNameLimit !== null) { |
|||
367 | 8 | return $this->_migrationNameLimit; |
|||
368 | } |
||||
369 | 136 | $tableSchema = $this->db->schema ? $this->db->schema->getTableSchema($this->migrationTable, true) : null; |
|||
370 | 136 | if ($tableSchema !== null) { |
|||
371 | 52 | return $this->_migrationNameLimit = $tableSchema->columns['version']->size; |
|||
372 | } |
||||
373 | |||||
374 | 84 | return static::MAX_NAME_LENGTH; |
|||
375 | } |
||||
376 | |||||
377 | /** |
||||
378 | * Normalizes table name for generator. |
||||
379 | * When name is preceded with underscore name case is kept - otherwise it's converted from camelcase to underscored. |
||||
380 | * Last underscore is always trimmed so if there should be underscore at the end of name use two of them. |
||||
381 | * @param string $name |
||||
382 | * @return string |
||||
383 | */ |
||||
384 | 80 | private function normalizeTableName($name) |
|||
385 | { |
||||
386 | 80 | if (substr($name, -1) === '_') { |
|||
387 | 60 | $name = substr($name, 0, -1); |
|||
388 | } |
||||
389 | |||||
390 | 80 | if (strncmp($name, '_', 1) === 0) { |
|||
391 | 56 | return substr($name, 1); |
|||
392 | } |
||||
393 | |||||
394 | 26 | return Inflector::underscore($name); |
|||
395 | } |
||||
396 | |||||
397 | /** |
||||
398 | * {@inheritdoc} |
||||
399 | * @since 2.0.8 |
||||
400 | */ |
||||
401 | 84 | protected function generateMigrationSourceCode($params) |
|||
402 | { |
||||
403 | 84 | $parsedFields = $this->parseFields(); |
|||
404 | 84 | $fields = $parsedFields['fields']; |
|||
405 | 84 | $foreignKeys = $parsedFields['foreignKeys']; |
|||
406 | |||||
407 | 84 | $name = $params['name']; |
|||
408 | 84 | if ($params['namespace']) { |
|||
409 | 82 | $name = substr($name, (strrpos($name, '\\') ?: -1) + 1); |
|||
410 | } |
||||
411 | |||||
412 | 84 | $templateFile = $this->templateFile; |
|||
413 | 84 | $table = null; |
|||
414 | 84 | if (preg_match('/^create_?junction_?(?:table)?_?(?:for)?(.+)_?and(.+)_?tables?$/i', $name, $matches)) { |
|||
415 | 15 | $templateFile = $this->generatorTemplateFiles['create_junction']; |
|||
416 | 15 | $firstTable = $this->normalizeTableName($matches[1]); |
|||
417 | 15 | $secondTable = $this->normalizeTableName($matches[2]); |
|||
418 | |||||
419 | 15 | $fields = array_merge( |
|||
420 | 15 | [ |
|||
421 | 15 | [ |
|||
422 | 15 | 'property' => $firstTable . '_id', |
|||
423 | 15 | 'decorators' => 'integer()', |
|||
424 | 15 | ], |
|||
425 | 15 | [ |
|||
426 | 15 | 'property' => $secondTable . '_id', |
|||
427 | 15 | 'decorators' => 'integer()', |
|||
428 | 15 | ], |
|||
429 | 15 | ], |
|||
430 | 15 | $fields, |
|||
431 | 15 | [ |
|||
432 | 15 | [ |
|||
433 | 15 | 'property' => 'PRIMARY KEY(' . |
|||
434 | 15 | $firstTable . '_id, ' . |
|||
435 | 15 | $secondTable . '_id)', |
|||
436 | 15 | ], |
|||
437 | 15 | ] |
|||
438 | 15 | ); |
|||
439 | |||||
440 | 15 | $foreignKeys[$firstTable . '_id']['table'] = $firstTable; |
|||
441 | 15 | $foreignKeys[$secondTable . '_id']['table'] = $secondTable; |
|||
442 | 15 | $foreignKeys[$firstTable . '_id']['column'] = null; |
|||
443 | 15 | $foreignKeys[$secondTable . '_id']['column'] = null; |
|||
444 | 15 | $table = $firstTable . '_' . $secondTable; |
|||
445 | 69 | } elseif (preg_match('/^add(.+)columns?_?to(.+)table$/i', $name, $matches)) { |
|||
446 | 15 | $templateFile = $this->generatorTemplateFiles['add_column']; |
|||
447 | 15 | $table = $this->normalizeTableName($matches[2]); |
|||
448 | 54 | } elseif (preg_match('/^drop(.+)columns?_?from(.+)table$/i', $name, $matches)) { |
|||
449 | 11 | $templateFile = $this->generatorTemplateFiles['drop_column']; |
|||
450 | 11 | $table = $this->normalizeTableName($matches[2]); |
|||
451 | 43 | } elseif (preg_match('/^create(.+)table$/i', $name, $matches)) { |
|||
452 | 27 | $this->addDefaultPrimaryKey($fields); |
|||
453 | 27 | $templateFile = $this->generatorTemplateFiles['create_table']; |
|||
454 | 27 | $table = $this->normalizeTableName($matches[1]); |
|||
455 | 16 | } elseif (preg_match('/^drop(.+)table$/i', $name, $matches)) { |
|||
456 | 12 | $this->addDefaultPrimaryKey($fields); |
|||
457 | 12 | $templateFile = $this->generatorTemplateFiles['drop_table']; |
|||
458 | 12 | $table = $this->normalizeTableName($matches[1]); |
|||
459 | } |
||||
460 | |||||
461 | 84 | foreach ($foreignKeys as $column => $foreignKey) { |
|||
462 | 23 | $relatedColumn = $foreignKey['column']; |
|||
463 | 23 | $relatedTable = $foreignKey['table']; |
|||
464 | // Since 2.0.11 if related column name is not specified, |
||||
465 | // we're trying to get it from table schema |
||||
466 | // @see https://github.com/yiisoft/yii2/issues/12748 |
||||
467 | 23 | if ($relatedColumn === null) { |
|||
468 | 23 | $relatedColumn = 'id'; |
|||
469 | try { |
||||
470 | 23 | $this->db = Instance::ensure($this->db, Connection::className()); |
|||
471 | 23 | $relatedTableSchema = $this->db->getTableSchema($relatedTable); |
|||
472 | 23 | if ($relatedTableSchema !== null) { |
|||
473 | $primaryKeyCount = count($relatedTableSchema->primaryKey); |
||||
474 | if ($primaryKeyCount === 1) { |
||||
475 | $relatedColumn = $relatedTableSchema->primaryKey[0]; |
||||
476 | } elseif ($primaryKeyCount > 1) { |
||||
477 | $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); |
||||
478 | } elseif ($primaryKeyCount === 0) { |
||||
479 | 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); |
|||
480 | } |
||||
481 | } |
||||
482 | } catch (\ReflectionException $e) { |
||||
483 | $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); |
||||
484 | } |
||||
485 | } |
||||
486 | 23 | $foreignKeys[$column] = [ |
|||
487 | 23 | 'idx' => $this->generateTableName("idx-$table-$column"), |
|||
488 | 23 | 'fk' => $this->generateTableName("fk-$table-$column"), |
|||
489 | 23 | 'relatedTable' => $this->generateTableName($relatedTable), |
|||
490 | 23 | 'relatedColumn' => $relatedColumn, |
|||
491 | 23 | ]; |
|||
492 | } |
||||
493 | |||||
494 | 84 | return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [ |
|||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
495 | 84 | 'table' => $this->generateTableName($table), |
|||
496 | 84 | 'fields' => $fields, |
|||
497 | 84 | 'foreignKeys' => $foreignKeys, |
|||
498 | 84 | 'tableComment' => $this->comment, |
|||
499 | 84 | ])); |
|||
500 | } |
||||
501 | |||||
502 | /** |
||||
503 | * If `useTablePrefix` equals true, then the table name will contain the |
||||
504 | * prefix format. |
||||
505 | * |
||||
506 | * @param string $tableName the table name to generate. |
||||
507 | * @return string |
||||
508 | * @since 2.0.8 |
||||
509 | */ |
||||
510 | 84 | protected function generateTableName($tableName) |
|||
511 | { |
||||
512 | 84 | if (!$this->useTablePrefix) { |
|||
513 | return $tableName; |
||||
514 | } |
||||
515 | |||||
516 | 84 | return '{{%' . $tableName . '}}'; |
|||
517 | } |
||||
518 | |||||
519 | /** |
||||
520 | * Parse the command line migration fields. |
||||
521 | * @return array parse result with following fields: |
||||
522 | * |
||||
523 | * - fields: array, parsed fields |
||||
524 | * - foreignKeys: array, detected foreign keys |
||||
525 | * |
||||
526 | * @since 2.0.7 |
||||
527 | */ |
||||
528 | 84 | protected function parseFields() |
|||
529 | { |
||||
530 | 84 | $fields = []; |
|||
531 | 84 | $foreignKeys = []; |
|||
532 | |||||
533 | 84 | foreach ($this->fields as $index => $field) { |
|||
534 | 46 | $chunks = $this->splitFieldIntoChunks($field); |
|||
535 | 46 | $property = array_shift($chunks); |
|||
0 ignored issues
–
show
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
Loading history...
|
|||||
536 | |||||
537 | 46 | foreach ($chunks as $i => &$chunk) { |
|||
538 | 46 | if (strncmp($chunk, 'foreignKey', 10) === 0) { |
|||
539 | 8 | preg_match('/foreignKey\((\w*)\s?(\w*)\)/', $chunk, $matches); |
|||
540 | 8 | $foreignKeys[$property] = [ |
|||
541 | 8 | 'table' => isset($matches[1]) |
|||
542 | 8 | ? $matches[1] |
|||
543 | 8 | : preg_replace('/_id$/', '', $property), |
|||
544 | 8 | 'column' => !empty($matches[2]) |
|||
545 | ? $matches[2] |
||||
546 | : null, |
||||
547 | 8 | ]; |
|||
548 | |||||
549 | 8 | unset($chunks[$i]); |
|||
550 | 8 | continue; |
|||
551 | } |
||||
552 | |||||
553 | 46 | if (!preg_match('/^(.+?)\(([^(]+)\)$/', $chunk)) { |
|||
554 | 46 | $chunk .= '()'; |
|||
555 | } |
||||
556 | } |
||||
557 | 46 | $fields[] = [ |
|||
558 | 46 | 'property' => $property, |
|||
559 | 46 | 'decorators' => implode('->', $chunks), |
|||
560 | 46 | ]; |
|||
561 | } |
||||
562 | |||||
563 | 84 | return [ |
|||
564 | 84 | 'fields' => $fields, |
|||
565 | 84 | 'foreignKeys' => $foreignKeys, |
|||
566 | 84 | ]; |
|||
567 | } |
||||
568 | |||||
569 | /** |
||||
570 | * Splits field into chunks |
||||
571 | * |
||||
572 | * @param string $field |
||||
573 | * @return string[]|false |
||||
574 | */ |
||||
575 | 46 | protected function splitFieldIntoChunks($field) |
|||
576 | { |
||||
577 | 46 | $originalDefaultValue = null; |
|||
578 | 46 | $defaultValue = null; |
|||
579 | 46 | preg_match_all('/defaultValue\(["\'].*?:?.*?["\']\)/', $field, $matches, PREG_SET_ORDER, 0); |
|||
580 | 46 | if (isset($matches[0][0])) { |
|||
581 | 5 | $originalDefaultValue = $matches[0][0]; |
|||
582 | 5 | $defaultValue = str_replace(':', '{{colon}}', $originalDefaultValue); |
|||
583 | 5 | $field = str_replace($originalDefaultValue, $defaultValue, $field); |
|||
584 | } |
||||
585 | |||||
586 | 46 | $chunks = preg_split('/\s?:\s?/', $field); |
|||
587 | |||||
588 | 46 | if (is_array($chunks) && $defaultValue !== null && $originalDefaultValue !== null) { |
|||
589 | 5 | foreach ($chunks as $key => $chunk) { |
|||
590 | 5 | $chunks[$key] = str_replace($defaultValue, $originalDefaultValue, $chunk); |
|||
591 | } |
||||
592 | } |
||||
593 | |||||
594 | 46 | return $chunks; |
|||
595 | } |
||||
596 | |||||
597 | /** |
||||
598 | * Adds default primary key to fields list if there's no primary key specified. |
||||
599 | * @param array $fields parsed fields |
||||
600 | * @since 2.0.7 |
||||
601 | */ |
||||
602 | 39 | protected function addDefaultPrimaryKey(&$fields) |
|||
603 | { |
||||
604 | 39 | foreach ($fields as $field) { |
|||
605 | 20 | if ($field['property'] === 'id' || false !== strripos($field['decorators'], 'primarykey()')) { |
|||
606 | 10 | return; |
|||
607 | } |
||||
608 | } |
||||
609 | 29 | array_unshift($fields, ['property' => 'id', 'decorators' => 'primaryKey()']); |
|||
610 | } |
||||
611 | } |
||||
612 |