Completed
Push — 4.x ( 7a7a18...d1d2ef )
by Kit Loong
13:17 queued 04:12
created

MigrateGenerateCommand::handle()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 16
rs 10
ccs 0
cts 10
cp 0
crap 6
1
<?php namespace KitLoong\MigrationsGenerator;
2
3
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
4
use Illuminate\Support\Facades\Config;
5
use Illuminate\Support\Str;
6
use KitLoong\MigrationsGenerator\Generators\Decorator;
7
use KitLoong\MigrationsGenerator\Generators\SchemaGenerator;
8
use Way\Generators\Commands\GeneratorCommand;
9
use Way\Generators\Generator;
10
use Xethron\MigrationsGenerator\Syntax\AddForeignKeysToTable;
11
use Xethron\MigrationsGenerator\Syntax\AddToTable;
12
use Xethron\MigrationsGenerator\Syntax\DroppedTable;
13
use Xethron\MigrationsGenerator\Syntax\RemoveForeignKeysFromTable;
14
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
    protected $decorator;
93
94
    public function __construct(
95
        Generator $generator,
96
        SchemaGenerator $schemaGenerator,
97
        MigrationRepositoryInterface $repository,
98
        Decorator $decorator
99
    ) {
100
        $this->schemaGenerator = $schemaGenerator;
101
        $this->repository = $repository;
102
        $this->decorator = $decorator;
103
104
        parent::__construct($generator);
105
    }
106
107
    /**
108
     * Execute the console command. Added for Laravel 5.5
109
     *
110
     * @return void
111
     * @throws \Doctrine\DBAL\DBALException
112
     */
113
    public function handle()
114
    {
115
        $this->setup($this->connection = $this->option('connection') ?: Config::get('database.default'));
116
117
        $this->info('Using connection: '.$this->connection."\n");
118
119
        $this->schemaGenerator->initialize();
120
121
        $tables = $this->filterTables();
122
        $this->info('Generating migrations for: '.implode(', ', $tables));
123
124
        $this->askIfLogMigrationTable();
125
126
        $this->generateMigrationFiles($tables);
127
128
        $this->info("\nFinished!\n");
129
    }
130
131
    protected function setup(string $connection): void
132
    {
133
        /** @var MigrationsGeneratorSetting $setting */
134
        $setting = app(MigrationsGeneratorSetting::class);
135
        $setting->setConnection($connection);
136
        $setting->setIgnoreIndexNames($this->option('defaultIndexNames'));
0 ignored issues
show
Bug introduced by
$this->option('defaultIndexNames') of type string is incompatible with the type boolean expected by parameter $ignoreIndexNames of KitLoong\MigrationsGener...::setIgnoreIndexNames(). ( Ignorable by Annotation )

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

136
        $setting->setIgnoreIndexNames(/** @scrutinizer ignore-type */ $this->option('defaultIndexNames'));
Loading history...
137
        $setting->setIgnoreForeignKeyNames($this->option('defaultFKNames'));
0 ignored issues
show
Bug introduced by
$this->option('defaultFKNames') of type string is incompatible with the type boolean expected by parameter $ignoreForeignKeyNames of KitLoong\MigrationsGener...IgnoreForeignKeyNames(). ( Ignorable by Annotation )

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

137
        $setting->setIgnoreForeignKeyNames(/** @scrutinizer ignore-type */ $this->option('defaultFKNames'));
Loading history...
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 filterTables()
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 askIfLogMigrationTable(): void
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
            $this->batch = $this->askNumeric(
181
                'Next Batch Number is: '.$this->repository->getNextBatchNumber().'. We recommend using Batch Number 0 so that it becomes the "first" migration',
182
                0
183
            );
184
        }
185
    }
186
187
    protected function generateMigrationFiles(array $tables): void
188
    {
189
        $this->info("Setting up Tables and Index Migrations");
190
        $this->datePrefix = date('Y_m_d_His');
191
        $this->generateTablesAndIndices($tables);
192
193
        $this->info("\nSetting up Foreign Key Migrations\n");
194
        $this->datePrefix = date('Y_m_d_His', strtotime('+1 second'));
195
        $this->generateForeignKeys($tables);
196
    }
197
198
    /**
199
     * Ask for user input: Yes/No.
200
     *
201
     * @param  string  $question  Question to ask
202
     * @return boolean          Answer from user
203
     */
204
    protected function askYn(string $question): bool
205
    {
206
        $answer = $this->ask($question.' [Y/n] ') ?? 'y';
207
208
        while (!in_array(strtolower($answer), ['y', 'n', 'yes', 'no'])) {
209
            $answer = $this->ask('Please choose either yes or no. [Y/n]') ?? 'y';
210
        }
211
        return in_array(strtolower($answer), ['y', 'yes']);
212
    }
213
214
    /**
215
     * Ask user for a Numeric Value, or blank for default.
216
     *
217
     * @param  string  $question  Question to ask
218
     * @param  int|null  $default  Default Value (optional)
219
     * @return int           Answer
220
     */
221
    protected function askNumeric(string $question, $default = null): int
222
    {
223
        $ask = 'Your answer needs to be a numeric value';
224
225
        if (!is_null($default)) {
226
            $question .= ' [Default: '.$default.'] ';
227
            $ask .= ' or blank for default';
228
        }
229
230
        $answer = $this->ask($question);
231
232
        while (!is_numeric($answer) and !($answer == '' and !is_null($default))) {
233
            $answer = $this->ask($ask.'. ');
234
        }
235
        if ($answer == '') {
236
            $answer = $default;
237
        }
238
        return $answer;
239
    }
240
241
    /**
242
     * Generate tables and index migrations.
243
     *
244
     * @param  string[]  $tables  List of tables to create migrations for
245
     * @return void
246
     */
247
    protected function generateTablesAndIndices($tables)
248
    {
249
        $this->method = 'create';
250
251
        foreach ($tables as $tableName) {
252
            $this->table = $tableName;
253
            $this->migrationName = 'create_'.$this->decorator->tableUsedInFilename($tableName).'_table';
254
            $indexes = $this->schemaGenerator->getIndexes($table = $this->schemaGenerator->getTable($tableName));
255
256
            $fields = $this->schemaGenerator->getFields($table, $indexes['single']);
257
            $this->fields = array_merge($fields, $indexes['multi']->toArray());
258
259
            $this->generate();
260
        }
261
    }
262
263
    /**
264
     * Generate foreign key migrations.
265
     *
266
     * @param  array  $tables  List of tables to create migrations for
267
     * @return void
268
     */
269
    protected function generateForeignKeys(array $tables)
270
    {
271
        $this->method = 'table';
272
273
        foreach ($tables as $tableName) {
274
            $this->table = $tableName;
275
            $this->migrationName = 'add_foreign_keys_to_'.$this->decorator->tableUsedInFilename($tableName).'_table';
276
            $this->fields = $this->schemaGenerator->getForeignKeyConstraints($tableName);
277
278
            $this->generate();
279
        }
280
    }
281
282
    /**
283
     * Generate Migration for the current table.
284
     *
285
     * @return void
286
     */
287
    protected function generate()
288
    {
289
        if (!empty($this->fields)) {
290
            $this->create();
291
292
            if ($this->log) {
293
                $file = $this->datePrefix.'_'.$this->migrationName;
294
                $this->repository->log($file, $this->batch);
295
            }
296
        }
297
    }
298
299
    /**
300
     * The path where the file will be created.
301
     *
302
     * @return string
303
     */
304
    protected function getFileGenerationPath(): string
305
    {
306
        $path = $this->getPathByOptionOrConfig('path', 'migration_target_path');
307
        $fileName = $this->datePrefix.'_'.$this->migrationName.'.php';
308
309
        return "{$path}/{$fileName}";
310
    }
311
312
    /**
313
     * Fetch the template data.
314
     *
315
     * @return array
316
     */
317
    protected function getTemplateData(): array
318
    {
319
        if ($this->method == 'create') {
320
            $up = app(AddToTable::class)->run(
321
                $this->fields,
322
                $this->table,
323
                $this->connection,
324
                'create'
325
            );
326
            $down = app(DroppedTable::class)->run(
327
                $this->fields,
328
                $this->table,
329
                $this->connection,
330
                'drop'
331
            );
332
        } else {
333
            $up = app(AddForeignKeysToTable::class)->run(
334
                $this->fields,
335
                $this->table,
336
                $this->connection
337
            );
338
            $down = app(RemoveForeignKeysFromTable::class)->run(
339
                $this->fields,
340
                $this->table,
341
                $this->connection
342
            );
343
        }
344
345
        return [
346
            'CLASS' => ucwords(Str::camel($this->migrationName)),
347
            'UP' => $up,
348
            'DOWN' => $down
349
        ];
350
    }
351
352
    /**
353
     * Get path to template for generator.
354
     *
355
     * @return string
356
     */
357
    protected function getTemplatePath(): string
358
    {
359
        return $this->getPathByOptionOrConfig('templatePath', 'migration_template_path');
360
    }
361
362
    /**
363
     * Remove all the tables to exclude from the array of tables.
364
     *
365
     * @param  string[]  $tables
366
     *
367
     * @return string[]
368
     */
369
    protected function filterAndExcludeTables($tables)
370
    {
371
        $excludes = $this->getExcludedTables();
372
        $tables = array_diff($tables, $excludes);
373
374
        return $tables;
375
    }
376
377
    /**
378
     * Get a list of tables to be excluded.
379
     *
380
     * @return string[]
381
     */
382
    protected function getExcludedTables()
383
    {
384
        /** @var MigrationsGeneratorSetting $setting */
385
        $setting = app(MigrationsGeneratorSetting::class);
386
387
        $excludes = [$setting->getConnection()->getTablePrefix().Config::get('database.migrations')];
388
        $ignore = (string) $this->option('ignore');
389
        if (!empty($ignore)) {
390
            return array_merge($excludes, explode(',', $ignore));
391
        }
392
393
        return $excludes;
394
    }
395
}
396