Completed
Push — 4.x ( 61b761...7a7a18 )
by Kit Loong
05:10
created

MigrateGenerateCommand::generate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php namespace Xethron\MigrationsGenerator;
2
3
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
4
use Illuminate\Support\Facades\Config;
5
use Illuminate\Support\Str;
6
use KitLoong\MigrationsGenerator\Generators\SchemaGenerator;
7
use KitLoong\MigrationsGenerator\MigrationGeneratorSetting;
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
    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'),
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...Generator::initialize(). ( Ignorable by Annotation )

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

120
            /** @scrutinizer ignore-type */ $this->option('defaultIndexNames'),
Loading history...
121
            $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...Generator::initialize(). ( Ignorable by Annotation )

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

121
            /** @scrutinizer ignore-type */ $this->option('defaultFKNames')
Loading history...
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
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
        $excludes = [Config::get('database.migrations')];
387
        $ignore = (string) $this->option('ignore');
388
        if (!empty($ignore)) {
389
            return array_merge($excludes, explode(',', $ignore));
390
        }
391
392
        return $excludes;
393
    }
394
}
395