1 | <?php namespace Xethron\MigrationsGenerator; |
||
15 | class MigrateGenerateCommand extends GeneratorCommand |
||
16 | { |
||
17 | |||
18 | /** |
||
19 | * The name and signature of the console command. |
||
20 | * @var string |
||
21 | */ |
||
22 | protected $signature = 'migrate:generate |
||
23 | {tables? : A list of Tables you wish to Generate Migrations for separated by a comma: users,posts,comments} |
||
24 | {--c|connection= : The database connection to use} |
||
25 | {--t|tables= : A list of Tables you wish to Generate Migrations for separated by a comma: users,posts,comments} |
||
26 | {--i|ignore= : A list of Tables you wish to ignore, separated by a comma: users,posts,comments} |
||
27 | {--p|path= : Where should the file be created?} |
||
28 | {--tp|templatePath= : The location of the template for this generator} |
||
29 | {--defaultIndexNames : Don\'t use db index names for migrations} |
||
30 | {--defaultFKNames : Don\'t use db foreign key names for migrations}'; |
||
31 | |||
32 | /** |
||
33 | * The console command description. |
||
34 | */ |
||
35 | protected $description = 'Generate a migration from an existing table structure.'; |
||
36 | |||
37 | /** |
||
38 | * @var MigrationRepositoryInterface $repository |
||
39 | */ |
||
40 | protected $repository; |
||
41 | |||
42 | /** |
||
43 | * @var SchemaGenerator |
||
44 | */ |
||
45 | protected $schemaGenerator; |
||
46 | |||
47 | /** |
||
48 | * Array of Fields to create in a new Migration |
||
49 | * Namely: Columns, Indexes and Foreign Keys |
||
50 | */ |
||
51 | protected $fields = array(); |
||
52 | |||
53 | /** |
||
54 | * List of Migrations that has been done |
||
55 | */ |
||
56 | protected $migrations = array(); |
||
57 | |||
58 | protected $log = false; |
||
59 | |||
60 | /** |
||
61 | * @var int |
||
62 | */ |
||
63 | protected $batch; |
||
64 | |||
65 | /** |
||
66 | * Filename date prefix (Y_m_d_His) |
||
67 | * @var string |
||
68 | */ |
||
69 | protected $datePrefix; |
||
70 | |||
71 | /** |
||
72 | * @var string |
||
73 | */ |
||
74 | protected $migrationName; |
||
75 | |||
76 | /** |
||
77 | * @var string |
||
78 | */ |
||
79 | protected $method; |
||
80 | |||
81 | /** |
||
82 | * @var string |
||
83 | */ |
||
84 | protected $table; |
||
85 | |||
86 | /** |
||
87 | * Will append connection method if not default connection |
||
88 | * @var string |
||
89 | */ |
||
90 | protected $connection; |
||
91 | |||
92 | public function __construct( |
||
93 | Generator $generator, |
||
94 | SchemaGenerator $schemaGenerator, |
||
95 | MigrationRepositoryInterface $repository |
||
96 | ) { |
||
97 | $this->schemaGenerator = $schemaGenerator; |
||
98 | $this->repository = $repository; |
||
99 | |||
100 | parent::__construct($generator); |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * Execute the console command. Added for Laravel 5.5 |
||
105 | * |
||
106 | * @return void |
||
107 | * @throws \Doctrine\DBAL\DBALException |
||
108 | */ |
||
109 | public function handle() |
||
110 | { |
||
111 | /** @var MigrationGeneratorSetting $setting */ |
||
112 | $setting = app(MigrationGeneratorSetting::class); |
||
113 | |||
114 | $this->connection = $this->option('connection') ?: Config::get('database.default'); |
||
115 | $setting->setConnection($this->connection); |
||
116 | $this->info('Using connection: '.$this->connection."\n"); |
||
117 | |||
118 | $this->schemaGenerator->initialize( |
||
119 | $this->connection, |
||
120 | $this->option('defaultIndexNames'), |
||
121 | $this->option('defaultFKNames') |
||
122 | ); |
||
123 | |||
124 | $tables = $this->getTables(); |
||
125 | $this->info('Generating migrations for: '.implode(', ', $tables)); |
||
126 | |||
127 | $this->logMigrationTable(); |
||
128 | |||
129 | $this->info("Setting up Tables and Index Migrations"); |
||
130 | $this->datePrefix = date('Y_m_d_His'); |
||
131 | $this->generateTablesAndIndices($tables); |
||
132 | |||
133 | $this->info("\nSetting up Foreign Key Migrations\n"); |
||
134 | $this->datePrefix = date('Y_m_d_His', strtotime('+1 second')); |
||
135 | $this->generateForeignKeys($tables); |
||
136 | |||
137 | $this->info("\nFinished!\n"); |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Get all tables from schema or return table list provided in option. |
||
142 | * Then filter and exclude tables in --ignore option if any. |
||
143 | * Also exclude migrations table |
||
144 | * |
||
145 | * @return string[] |
||
146 | */ |
||
147 | protected function getTables() |
||
148 | { |
||
149 | if ($tableArg = (string) $this->argument('tables')) { |
||
150 | $tables = explode(',', $tableArg); |
||
151 | } elseif ($tableOpt = (string) $this->option('tables')) { |
||
152 | $tables = explode(',', $tableOpt); |
||
153 | } else { |
||
154 | $tables = $this->schemaGenerator->getTables(); |
||
155 | } |
||
156 | |||
157 | return $this->filterAndExcludeTables($tables); |
||
158 | } |
||
159 | |||
160 | protected function logMigrationTable() |
||
161 | { |
||
162 | if (!$this->option('no-interaction')) { |
||
163 | $this->log = $this->askYn('Do you want to log these migrations in the migrations table?'); |
||
164 | } |
||
165 | |||
166 | if ($this->log) { |
||
167 | $migrationSource = $this->connection; |
||
168 | |||
169 | if ($migrationSource !== Config::get('database.default')) { |
||
170 | if (!$this->askYn('Log into current connection: '.$this->connection.'? [Y = '.$this->connection.', n = '.Config::get('database.default').' (default connection)]')) { |
||
171 | $migrationSource = Config::get('database.default'); |
||
172 | } |
||
173 | } |
||
174 | |||
175 | $this->repository->setSource($migrationSource); |
||
176 | if (!$this->repository->repositoryExists()) { |
||
177 | $options = array('--database' => $migrationSource); |
||
178 | $this->call('migrate:install', $options); |
||
179 | } |
||
180 | $batch = $this->repository->getNextBatchNumber(); |
||
181 | $this->batch = $this->askNumeric( |
||
182 | 'Next Batch Number is: '.$batch.'. We recommend using Batch Number 0 so that it becomes the "first" migration', |
||
183 | 0 |
||
184 | ); |
||
185 | } |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Ask for user input: Yes/No. |
||
190 | * |
||
191 | * @param string $question Question to ask |
||
192 | * @return boolean Answer from user |
||
193 | */ |
||
194 | protected function askYn(string $question): bool |
||
195 | { |
||
196 | $answer = $this->ask($question.' [Y/n] ') ?? 'y'; |
||
197 | |||
198 | while (!in_array(strtolower($answer), ['y', 'n', 'yes', 'no'])) { |
||
199 | $answer = $this->ask('Please choose either yes or no. [Y/n]') ?? 'y'; |
||
200 | } |
||
201 | return in_array(strtolower($answer), ['y', 'yes']); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Ask user for a Numeric Value, or blank for default. |
||
206 | * |
||
207 | * @param string $question Question to ask |
||
208 | * @param int|null $default Default Value (optional) |
||
209 | * @return int Answer |
||
210 | */ |
||
211 | protected function askNumeric(string $question, $default = null): int |
||
212 | { |
||
213 | $ask = 'Your answer needs to be a numeric value'; |
||
214 | |||
215 | if (!is_null($default)) { |
||
216 | $question .= ' [Default: '.$default.'] '; |
||
217 | $ask .= ' or blank for default'; |
||
218 | } |
||
219 | |||
220 | $answer = $this->ask($question); |
||
221 | |||
222 | while (!is_numeric($answer) and !($answer == '' and !is_null($default))) { |
||
223 | $answer = $this->ask($ask.'. '); |
||
224 | } |
||
225 | if ($answer == '') { |
||
226 | $answer = $default; |
||
227 | } |
||
228 | return $answer; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Generate tables and index migrations. |
||
233 | * |
||
234 | * @param string[] $tables List of tables to create migrations for |
||
235 | * @return void |
||
236 | */ |
||
237 | protected function generateTablesAndIndices($tables) |
||
238 | { |
||
239 | $this->method = 'create'; |
||
240 | |||
241 | foreach ($tables as $tableName) { |
||
242 | $this->table = $tableName; |
||
243 | $this->migrationName = 'create_'.preg_replace('/[^a-zA-Z0-9_]/', '_', $this->table).'_table'; |
||
244 | $table = $this->schemaGenerator->getTable($tableName); |
||
245 | $indexes = $this->schemaGenerator->getIndexes($table); |
||
246 | $singleColIndexes = $indexes['single']; |
||
247 | $multiColIndexes = $indexes['multi']; |
||
248 | $fields = $this->schemaGenerator->getFields($table, $singleColIndexes); |
||
249 | $this->fields = array_merge($fields, $multiColIndexes->toArray()); |
||
250 | |||
251 | $this->generate(); |
||
252 | } |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Generate foreign key migrations. |
||
257 | * |
||
258 | * @param array $tables List of tables to create migrations for |
||
259 | * @return void |
||
260 | */ |
||
261 | protected function generateForeignKeys(array $tables) |
||
262 | { |
||
263 | $this->method = 'table'; |
||
264 | |||
265 | foreach ($tables as $table) { |
||
266 | $this->table = $table; |
||
267 | $this->migrationName = 'add_foreign_keys_to_'.preg_replace('/[^a-zA-Z0-9_]/', '_', $this->table).'_table'; |
||
268 | $this->fields = $this->schemaGenerator->getForeignKeyConstraints($this->table); |
||
269 | |||
270 | $this->generate(); |
||
271 | } |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Generate Migration for the current table. |
||
276 | * |
||
277 | * @return void |
||
278 | */ |
||
279 | protected function generate() |
||
280 | { |
||
281 | if (!empty($this->fields)) { |
||
282 | $this->create(); |
||
283 | |||
284 | if ($this->log) { |
||
285 | $file = $this->datePrefix.'_'.$this->migrationName; |
||
286 | $this->repository->log($file, $this->batch); |
||
287 | } |
||
288 | } |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * The path where the file will be created. |
||
293 | * |
||
294 | * @return string |
||
295 | */ |
||
296 | protected function getFileGenerationPath(): string |
||
297 | { |
||
298 | $path = $this->getPathByOptionOrConfig('path', 'migration_target_path'); |
||
299 | $fileName = $this->getDatePrefix().'_'.$this->migrationName.'.php'; |
||
300 | |||
301 | return "{$path}/{$fileName}"; |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * Get the date prefix for the migration. |
||
306 | * |
||
307 | * @return string |
||
308 | */ |
||
309 | protected function getDatePrefix(): string |
||
310 | { |
||
311 | return $this->datePrefix; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Fetch the template data. |
||
316 | * |
||
317 | * @return array |
||
318 | */ |
||
319 | protected function getTemplateData(): array |
||
320 | { |
||
321 | if ($this->method == 'create') { |
||
322 | $up = app(AddToTable::class)->run( |
||
323 | $this->fields, |
||
324 | $this->table, |
||
325 | $this->connection, |
||
326 | 'create' |
||
327 | ); |
||
328 | $down = app(DroppedTable::class)->run( |
||
329 | $this->fields, |
||
330 | $this->table, |
||
331 | $this->connection, |
||
332 | 'drop' |
||
333 | ); |
||
334 | } else { |
||
335 | $up = app(AddForeignKeysToTable::class)->run( |
||
336 | $this->fields, |
||
337 | $this->table, |
||
338 | $this->connection |
||
339 | ); |
||
340 | $down = app(RemoveForeignKeysFromTable::class)->run( |
||
341 | $this->fields, |
||
342 | $this->table, |
||
343 | $this->connection |
||
344 | ); |
||
345 | } |
||
346 | |||
347 | return [ |
||
348 | 'CLASS' => ucwords(Str::camel($this->migrationName)), |
||
349 | 'UP' => $up, |
||
350 | 'DOWN' => $down |
||
351 | ]; |
||
352 | } |
||
353 | |||
354 | /** |
||
355 | * Get path to template for generator. |
||
356 | * |
||
357 | * @return string |
||
358 | */ |
||
359 | protected function getTemplatePath(): string |
||
363 | |||
364 | /** |
||
365 | * Remove all the tables to exclude from the array of tables. |
||
366 | * |
||
367 | * @param string[] $tables |
||
368 | * |
||
369 | * @return string[] |
||
370 | */ |
||
371 | protected function filterAndExcludeTables($tables) |
||
372 | { |
||
373 | $excludes = $this->getExcludedTables(); |
||
374 | $tables = array_diff($tables, $excludes); |
||
375 | |||
378 | |||
379 | /** |
||
380 | * Get a list of tables to be excluded. |
||
381 | * |
||
382 | * @return string[] |
||
383 | */ |
||
384 | protected function getExcludedTables() |
||
394 | } |
||
395 |