MigrateGenerateCommand::generate()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 8
rs 10
ccs 0
cts 7
cp 0
crap 12
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
195
        // Plus 1 second to have foreign key migrations generate after table migrations generated
196
        $this->datePrefix = date('Y_m_d_His', strtotime('+1 second'));
197
        $this->generateForeignKeys($tables);
198
    }
199
200
    /**
201
     * Ask for user input: Yes/No.
202
     *
203
     * @param  string  $question  Question to ask
204
     * @return boolean          Answer from user
205
     */
206
    protected function askYn(string $question): bool
207
    {
208
        $answer = $this->ask($question.' [Y/n] ') ?? 'y';
209
210
        while (!in_array(strtolower($answer), ['y', 'n', 'yes', 'no'])) {
211
            $answer = $this->ask('Please choose either yes or no. [Y/n]') ?? 'y';
212
        }
213
        return in_array(strtolower($answer), ['y', 'yes']);
214
    }
215
216
    /**
217
     * Ask user for a Numeric Value, or blank for default.
218
     *
219
     * @param  string  $question  Question to ask
220
     * @param  int|null  $default  Default Value (optional)
221
     * @return int           Answer
222
     */
223
    protected function askNumeric(string $question, $default = null): int
224
    {
225
        $ask = 'Your answer needs to be a numeric value';
226
227
        if (!is_null($default)) {
228
            $question .= ' [Default: '.$default.'] ';
229
            $ask .= ' or blank for default';
230
        }
231
232
        $answer = $this->ask($question);
233
234
        while (!is_numeric($answer) and !($answer == '' and !is_null($default))) {
235
            $answer = $this->ask($ask.'. ');
236
        }
237
        if ($answer == '') {
238
            $answer = $default;
239
        }
240
        return $answer;
241
    }
242
243
    /**
244
     * Generate tables and index migrations.
245
     *
246
     * @param  string[]  $tables  List of tables to create migrations for
247
     * @return void
248
     */
249
    protected function generateTablesAndIndices($tables)
250
    {
251
        $this->method = 'create';
252
253
        foreach ($tables as $tableName) {
254
            $this->table = $tableName;
255
            $this->migrationName = 'create_'.$this->decorator->tableUsedInFilename($tableName).'_table';
256
            $indexes = $this->schemaGenerator->getIndexes($tableName);
257
258
            $fields = $this->schemaGenerator->getFields($tableName, $indexes['single']);
259
            $this->fields = array_merge($fields, $indexes['multi']->toArray());
260
261
            $this->generate();
262
        }
263
    }
264
265
    /**
266
     * Generate foreign key migrations.
267
     *
268
     * @param  array  $tables  List of tables to create migrations for
269
     * @return void
270
     */
271
    protected function generateForeignKeys(array $tables)
272
    {
273
        $this->method = 'table';
274
275
        foreach ($tables as $tableName) {
276
            $this->table = $tableName;
277
            $this->migrationName = 'add_foreign_keys_to_'.$this->decorator->tableUsedInFilename($tableName).'_table';
278
            $this->fields = $this->schemaGenerator->getForeignKeyConstraints($tableName);
279
280
            $this->generate();
281
        }
282
    }
283
284
    /**
285
     * Generate Migration for the current table.
286
     *
287
     * @return void
288
     */
289
    protected function generate()
290
    {
291
        if (!empty($this->fields)) {
292
            $this->create();
293
294
            if ($this->log) {
295
                $file = $this->datePrefix.'_'.$this->migrationName;
296
                $this->repository->log($file, $this->batch);
297
            }
298
        }
299
    }
300
301
    /**
302
     * The path where the file will be created.
303
     *
304
     * @return string
305
     */
306
    protected function getFileGenerationPath(): string
307
    {
308
        $path = $this->getPathByOptionOrConfig('path', 'migration_target_path');
309
        $fileName = $this->datePrefix.'_'.$this->migrationName.'.php';
310
311
        return "{$path}/{$fileName}";
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
360
    {
361
        return $this->getPathByOptionOrConfig('templatePath', 'migration_template_path');
362
    }
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
376
        return $tables;
377
    }
378
379
    /**
380
     * Get a list of tables to be excluded.
381
     *
382
     * @return string[]
383
     */
384
    protected function getExcludedTables()
385
    {
386
        /** @var MigrationsGeneratorSetting $setting */
387
        $setting = app(MigrationsGeneratorSetting::class);
388
389
        $excludes = [$setting->getConnection()->getTablePrefix().Config::get('database.migrations')];
390
        $ignore = (string) $this->option('ignore');
391
        if (!empty($ignore)) {
392
            return array_merge($excludes, explode(',', $ignore));
393
        }
394
395
        return $excludes;
396
    }
397
}
398