Skip to content

Commit

Permalink
Migration commands refactoring (#83)
Browse files Browse the repository at this point in the history
* Migration commands refactoring

1. Added `split` option to the `cycle:migrate` command. This lets you break up changes into several migrations, each for a different table.
2. Now, if there are migrations you haven't done yet and you use the `cycle:migrate` command, it will ask if you want to run these migrations.
3. Put in a check for the `cycle:sync` command to stop it from running by mistake.
4. Made some changes to the messages you see in the console.

issue #82
  • Loading branch information
butschster authored Nov 21, 2023
1 parent aa7534f commit 56f1aa8
Show file tree
Hide file tree
Showing 18 changed files with 150 additions and 71 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"infection/infection": "^0.26.6",
"mockery/mockery": "^1.5",
"phpunit/phpunit": "^9.5.20",
"spiral/testing": "^2.0",
"spiral/testing": "^2.4",
"spiral/validator": "^1.2",
"vimeo/psalm": "^4.27"
},
Expand Down
2 changes: 2 additions & 0 deletions src/Auth/TokenStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ private function issueID(): string
{
$id = $this->randomHash(64);

/** @psalm-suppress InternalMethod */
$query = $this->orm->getSource(Token::class)
->getDatabase()
->select()
->from($this->orm->getSource(Token::class)->getTable());

/** @psalm-suppress InternalMethod */
while ((clone $query)->where('id', $id)->count('id') !== 0) {
$id = $this->randomHash(64);
}
Expand Down
23 changes: 12 additions & 11 deletions src/Console/Command/CycleOrm/Generator/ShowChanges.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,29 @@

namespace Spiral\Cycle\Console\Command\CycleOrm\Generator;

use Cycle\Database\Schema\ComparatorInterface;
use Cycle\Schema\GeneratorInterface;
use Cycle\Schema\Registry;
use Cycle\Database\Schema\AbstractTable;
use Cycle\Database\Schema\Comparator;
use Symfony\Component\Console\Output\OutputInterface;

final class ShowChanges implements GeneratorInterface
{
private array $changes = [];

public function __construct(
private readonly OutputInterface $output
private readonly OutputInterface $output,
) {
}

public function run(Registry $registry): Registry
{
$this->output->writeln('<info>Detecting schema changes:</info>');

$this->changes = [];
foreach ($registry->getIterator() as $e) {
if ($registry->hasTable($e)) {
$table = $registry->getTableSchema($e);
if ($table->getComparator()->hasChanges()) {
$key = $registry->getDatabase($e).':'.$registry->getTable($e);
$key = $registry->getDatabase($e) . ':' . $registry->getTable($e);
$this->changes[$key] = [
'database' => $registry->getDatabase($e),
'table' => $registry->getTable($e),
Expand All @@ -44,6 +42,9 @@ public function run(Registry $registry): Registry
return $registry;
}


$this->output->writeln('<info>Schema changes:</info>');

foreach ($this->changes as $change) {
$this->output->write(\sprintf('• <fg=cyan>%s.%s</fg=cyan>', $change['database'], $change['table']));
$this->describeChanges($change['schema']);
Expand All @@ -63,14 +64,14 @@ protected function describeChanges(AbstractTable $table): void
$this->output->writeln(
\sprintf(
': <fg=green>%s</fg=green> change(s) detected',
$this->numChanges($table)
)
$this->numChanges($table),
),
);

return;
}
$this->output->write("\n");

$this->output->write("\n");

if (!$table->exists()) {
$this->output->writeln(' - create table');
Expand All @@ -88,7 +89,7 @@ protected function describeChanges(AbstractTable $table): void
$this->describeFKs($cmp);
}

protected function describeColumns(Comparator $cmp): void
protected function describeColumns(ComparatorInterface $cmp): void
{
foreach ($cmp->addedColumns() as $column) {
$this->output->writeln(" - add column <fg=yellow>{$column->getName()}</fg=yellow>");
Expand All @@ -104,7 +105,7 @@ protected function describeColumns(Comparator $cmp): void
}
}

protected function describeIndexes(Comparator $cmp): void
protected function describeIndexes(ComparatorInterface $cmp): void
{
foreach ($cmp->addedIndexes() as $index) {
$index = \implode(', ', $index->getColumns());
Expand All @@ -123,7 +124,7 @@ protected function describeIndexes(Comparator $cmp): void
}
}

protected function describeFKs(Comparator $cmp): void
protected function describeFKs(ComparatorInterface $cmp): void
{
foreach ($cmp->addedForeignKeys() as $fk) {
$fkColumns = \implode(', ', $fk->getColumns());
Expand Down
30 changes: 24 additions & 6 deletions src/Console/Command/CycleOrm/MigrateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Spiral\Cycle\Console\Command\CycleOrm;

use Cycle\Schema\Generator\Migrations\Strategy\GeneratorStrategyInterface;
use Cycle\Schema\Generator\Migrations\Strategy\MultipleFilesStrategy;
use Spiral\Core\BinderInterface;
use Spiral\Cycle\Bootloader\SchemaBootloader;
use Spiral\Cycle\Config\CycleConfig;
use Spiral\Cycle\Console\Command\CycleOrm\Generator\ShowChanges;
Expand All @@ -22,6 +25,7 @@ final class MigrateCommand extends AbstractCommand
protected const NAME = 'cycle:migrate';
protected const DESCRIPTION = 'Generate ORM schema migrations';
protected const OPTIONS = [
['split', 'p', InputOption::VALUE_NONE, 'Split generated migration into multiple files.'],
['run', 'r', InputOption::VALUE_NONE, 'Automatically run generated migration.'],
];

Expand All @@ -30,30 +34,44 @@ public function perform(
CycleConfig $config,
Registry $registry,
MemoryInterface $memory,
GenerateMigrations $migrations,
Migrator $migrator,
Console $console
Console $console,
): int {
$migrator->configure();

foreach ($migrator->getMigrations() as $migration) {
if ($migration->getState()->getStatus() !== State::STATUS_EXECUTED) {
$this->writeln('<fg=red>Outstanding migrations found, run `migrate` first.</fg=red>');
return self::SUCCESS;
$this->error('Outstanding migrations found.');

if ($this->isInteractive() && $this->output->confirm('Do you want to run `migrate` now?')) {
$console->run('migrate', [], $this->output);
} else {
$this->error('Please run `migrate` first.');
return self::SUCCESS;
}
}
}

$this->comment('Detecting schema changes...');

$schemaCompiler = Compiler::compile(
$registry,
\array_merge($bootloader->getGenerators($config), [
$show = new ShowChanges($this->output)
$show = new ShowChanges($this->output),
]),
$config->getSchemaDefaults()
$config->getSchemaDefaults(),
);

$schemaCompiler->toMemory($memory);

if ($show->hasChanges()) {
if ($this->option('split')) {
\assert($this->container instanceof BinderInterface);
$this->container->bind(GeneratorStrategyInterface::class, MultipleFilesStrategy::class);
}

$migrations = $this->container->get(GenerateMigrations::class);

(new \Cycle\Schema\Compiler())->compile($registry, [$migrations]);

if ($this->option('run')) {
Expand Down
1 change: 0 additions & 1 deletion src/Console/Command/CycleOrm/RenderCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
final class RenderCommand extends AbstractCommand
{
protected const SIGNATURE = 'cycle:render {format=color : Output format}';

protected const DESCRIPTION = 'Render available CycleORM schemas';

public function perform(
Expand Down
15 changes: 10 additions & 5 deletions src/Console/Command/CycleOrm/SyncCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
use Cycle\Schema\Generator\SyncTables;
use Cycle\Schema\Registry;
use Spiral\Boot\MemoryInterface;
use Spiral\Console\Command;
use Spiral\Cycle\Config\CycleConfig;
use Spiral\Cycle\Console\Command\CycleOrm\Generator\ShowChanges;
use Spiral\Cycle\Console\Command\Migrate\AbstractCommand;
use Spiral\Cycle\Schema\Compiler;

final class SyncCommand extends Command
final class SyncCommand extends AbstractCommand
{
protected const NAME = 'cycle:sync';
protected const DESCRIPTION = 'Sync Cycle ORM schema with database without intermediate migration (risk operation)';
Expand All @@ -22,19 +22,24 @@ public function perform(
SchemaBootloader $bootloader,
CycleConfig $config,
Registry $registry,
MemoryInterface $memory
MemoryInterface $memory,
): int {
if (!$this->verifyEnvironment(message: 'This operation is not recommended for production environment.')) {
return self::FAILURE;
}

$show = new ShowChanges($this->output);

$schemaCompiler = Compiler::compile(
$registry,
\array_merge($bootloader->getGenerators($config), [$show, new SyncTables()]),
$config->getSchemaDefaults()
$config->getSchemaDefaults(),
);

$schemaCompiler->toMemory($memory);

if ($show->hasChanges()) {
$this->writeln("\n<info>ORM Schema has been synchronized</info>");
$this->info('ORM Schema has been synchronized with database.');
}

return self::SUCCESS;
Expand Down
8 changes: 4 additions & 4 deletions src/Console/Command/CycleOrm/UpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ public function perform(
SchemaBootloader $bootloader,
CycleConfig $config,
Registry $registry,
MemoryInterface $memory
MemoryInterface $memory,
): int {
$this->write('Updating ORM schema... ');
$this->info('Updating ORM schema... ');

Compiler::compile(
$registry,
$bootloader->getGenerators($config),
$config->getSchemaDefaults()
$config->getSchemaDefaults(),
)->toMemory($memory);

$this->writeln('<info>done</info>');
$this->info('Schema has been updated.');

return self::SUCCESS;
}
Expand Down
27 changes: 19 additions & 8 deletions src/Console/Command/Migrate/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class AbstractCommand extends Command
{
public function __construct(
protected Migrator $migrator,
protected MigrationConfig $config
protected MigrationConfig $config,
) {
parent::__construct();
}
Expand All @@ -24,7 +24,7 @@ protected function verifyConfigured(): bool
{
if (!$this->migrator->isConfigured()) {
$this->writeln(
"<fg=red>Migrations are not configured yet, run '<info>migrate:init</info>' first.</fg=red>"
"<fg=red>Migrations are not configured yet, run '<info>migrate:init</info>' first.</fg=red>",
);

return false;
Expand All @@ -36,17 +36,17 @@ protected function verifyConfigured(): bool
/**
* Check if current environment is safe to run migration.
*/
protected function verifyEnvironment(): bool
protected function verifyEnvironment(string $message = 'Confirmation is required to run migrations!'): bool
{
if ($this->option('force') || $this->config->isSafe()) {
if ($this->isForce() || $this->config->isSafe()) {
//Safe to run
return true;
}

$this->writeln('<fg=red>Confirmation is required to run migrations!</fg=red>');
$this->error($message);

if (!$this->askConfirmation()) {
$this->writeln('<comment>Cancelling operation...</comment>');
$this->comment('Cancelling operation...');

return false;
}
Expand All @@ -60,7 +60,8 @@ protected function defineOptions(): array
static::OPTIONS,
[
['force', 's', InputOption::VALUE_NONE, 'Skip safe environment check'],
]
['no-interaction', 'n', InputOption::VALUE_NONE, 'Do not ask any interactive question'],
],
);
}

Expand All @@ -71,7 +72,17 @@ protected function askConfirmation(): bool
return $question->ask(
$this->input,
$this->output,
new ConfirmationQuestion('<question>Would you like to continue?</question> ')
new ConfirmationQuestion('<question>Would you like to continue?</question> ', false),
);
}

protected function isInteractive(): bool
{
return !$this->option('no-interaction');
}

protected function isForce(): bool
{
return $this->option('force');
}
}
4 changes: 2 additions & 2 deletions src/Console/Command/Migrate/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
final class InitCommand extends AbstractCommand
{
protected const NAME = 'migrate:init';
protected const DESCRIPTION = 'Init migrations component (create migrations table)';
protected const DESCRIPTION = 'Create migrations table if not exists.';

/**
* Perform command.
*/
public function perform(): int
{
$this->migrator->configure();
$this->writeln('<info>Migrations table were successfully created</info>');
$this->info('Migration table was successfully created.');

return self::SUCCESS;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Console/Command/Migrate/MigrateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
final class MigrateCommand extends AbstractCommand
{
protected const NAME = 'migrate';
protected const DESCRIPTION = 'Perform one or all outstanding migrations';
protected const DESCRIPTION = 'Execute one or multiple migrations.';
protected const OPTIONS = [
['one', 'o', InputOption::VALUE_NONE, 'Execute only one (first) migration'],
];
Expand All @@ -34,12 +34,12 @@ public function perform(): int

$this->sprintf(
"<info>Migration <comment>%s</comment> was successfully executed.</info>\n",
$migration->getState()->getName()
$migration->getState()->getName(),
);
}

if (!$found) {
$this->writeln('<fg=red>No outstanding migrations were found.</fg=red>');
$this->error('No outstanding migrations were found.');
}

return self::SUCCESS;
Expand Down
5 changes: 2 additions & 3 deletions src/Console/Command/Migrate/ReplayCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ final class ReplayCommand extends AbstractCommand
];

/**
* @param Console $console
* @throws \Throwable
*/
public function perform(Console $console): int
Expand All @@ -35,12 +34,12 @@ public function perform(Console $console): int
$migrate['--one'] = true;
}

$this->writeln('Rolling back executed migration(s)...');
$this->warning('Rolling back executed migration(s)...');
$console->run('migrate:rollback', $rollback, $this->output);

$this->writeln('');

$this->writeln('Executing outstanding migration(s)...');
$this->info('Executing outstanding migration(s)...');
$console->run('migrate', $migrate, $this->output);

return self::SUCCESS;
Expand Down
Loading

0 comments on commit 56f1aa8

Please sign in to comment.