diff --git a/lib/Doctrine/Migrations/Tools/Console/Command/DiffCommand.php b/lib/Doctrine/Migrations/Tools/Console/Command/DiffCommand.php index 3d9de7f465..c5f897b345 100644 --- a/lib/Doctrine/Migrations/Tools/Console/Command/DiffCommand.php +++ b/lib/Doctrine/Migrations/Tools/Console/Command/DiffCommand.php @@ -5,6 +5,8 @@ namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Generator\Exception\NoChangesDetected; +use Doctrine\Migrations\Metadata\AvailableMigrationsList; +use Doctrine\Migrations\Metadata\ExecutedMigrationsSet; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use OutOfBoundsException; use Symfony\Component\Console\Input\InputInterface; @@ -13,6 +15,7 @@ use function addslashes; use function assert; use function class_exists; +use function count; use function filter_var; use function is_string; use function key; @@ -123,6 +126,18 @@ public function execute( assert(is_string($namespace)); + $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); + $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); + $newMigrations = $statusCalculator->getNewMigrations(); + + if ($this->checkNewMigrations($newMigrations, $input, $output) === false) { + return 3; + } + + if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input, $output) === false) { + return 3; + } + $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); $diffGenerator = $this->getDependencyFactory()->getDiffGenerator(); @@ -168,4 +183,60 @@ public function execute( return 0; } + + private function checkNewMigrations( + AvailableMigrationsList $newMigrations, + InputInterface $input, + OutputInterface $output + ) : bool { + if (count($newMigrations) !== 0) { + $output->writeln(sprintf( + 'WARNING! You have %s available migrations to execute.', + count($newMigrations) + )); + + $question = 'Are you sure you wish to continue? (y/n)'; + + if (! $this->canExecute($question, $input, $output)) { + $output->writeln('Migration cancelled!'); + + return false; + } + } + + return true; + } + + private function checkExecutedUnavailableMigrations( + ExecutedMigrationsSet $executedUnavailableMigrations, + InputInterface $input, + OutputInterface $output + ) : bool { + if (count($executedUnavailableMigrations) !== 0) { + $output->writeln(sprintf( + 'WARNING! You have %s previously executed migrations in the database that are not registered migrations.', + count($executedUnavailableMigrations) + )); + + foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) { + $output->writeln(sprintf( + ' >> %s (%s)', + $executedUnavailableMigration->getExecutedAt() !== null + ? $executedUnavailableMigration->getExecutedAt()->format('Y-m-d H:i:s') + : null, + $executedUnavailableMigration->getVersion() + )); + } + + $question = 'Are you sure you wish to continue? (y/n)'; + + if (! $this->canExecute($question, $input, $output)) { + $output->writeln('Migration cancelled!'); + + return false; + } + } + + return true; + } } diff --git a/tests/Doctrine/Migrations/Tests/Tools/Console/Command/DiffCommandTest.php b/tests/Doctrine/Migrations/Tests/Tools/Console/Command/DiffCommandTest.php index 739fe6fb0d..a246a2f4d9 100644 --- a/tests/Doctrine/Migrations/Tests/Tools/Console/Command/DiffCommandTest.php +++ b/tests/Doctrine/Migrations/Tests/Tools/Console/Command/DiffCommandTest.php @@ -4,11 +4,18 @@ namespace Doctrine\Migrations\Tests\Tools\Console\Command; +use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Generator\ClassNameGenerator; use Doctrine\Migrations\Generator\DiffGenerator; +use Doctrine\Migrations\Metadata\AvailableMigration; +use Doctrine\Migrations\Metadata\AvailableMigrationsList; +use Doctrine\Migrations\Metadata\ExecutedMigration; +use Doctrine\Migrations\Metadata\ExecutedMigrationsSet; use Doctrine\Migrations\Tools\Console\Command\DiffCommand; +use Doctrine\Migrations\Version\MigrationStatusCalculator; +use Doctrine\Migrations\Version\Version; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Input\InputInterface; @@ -20,6 +27,9 @@ final class DiffCommandTest extends TestCase /** @var DiffGenerator|MockObject */ private $migrationDiffGenerator; + /** @var MigrationStatusCalculator|MockObject */ + private $migrationStatusCalculator; + /** @var Configuration */ private $configuration; @@ -91,10 +101,57 @@ public function testExecute() : void $this->diffCommand->execute($input, $output); } + public function testAvailableMigrationsCancel() : void + { + $input = $this->createMock(InputInterface::class); + $output = $this->createMock(OutputInterface::class); + + $m1 = new AvailableMigration(new Version('A'), $this->createMock(AbstractMigration::class)); + + $this->migrationStatusCalculator + ->method('getNewMigrations') + ->willReturn(new AvailableMigrationsList([$m1])); + + $this->diffCommand->expects(self::once()) + ->method('canExecute') + ->with('Are you sure you wish to continue? (y/n)') + ->willReturn(false); + + $this->migrationDiffGenerator->expects(self::never())->method('generate'); + + $statusCode = $this->diffCommand->execute($input, $output); + + self::assertSame(3, $statusCode); + } + + public function testExecutedUnavailableMigrationsCancel() : void + { + $input = $this->createMock(InputInterface::class); + $output = $this->createMock(OutputInterface::class); + + $e1 = new ExecutedMigration(new Version('A')); + + $this->migrationStatusCalculator + ->method('getExecutedUnavailableMigrations') + ->willReturn(new ExecutedMigrationsSet([$e1])); + + $this->diffCommand->expects(self::once()) + ->method('canExecute') + ->with('Are you sure you wish to continue? (y/n)') + ->willReturn(false); + + $this->migrationDiffGenerator->expects(self::never())->method('generate'); + + $statusCode = $this->diffCommand->execute($input, $output); + + self::assertSame(3, $statusCode); + } + protected function setUp() : void { - $this->migrationDiffGenerator = $this->createMock(DiffGenerator::class); - $this->configuration = new Configuration(); + $this->migrationDiffGenerator = $this->createMock(DiffGenerator::class); + $this->migrationStatusCalculator = $this->createMock(MigrationStatusCalculator::class); + $this->configuration = new Configuration(); $this->configuration->addMigrationsDirectory('FooNs', sys_get_temp_dir()); $this->dependencyFactory = $this->createMock(DependencyFactory::class); @@ -117,6 +174,10 @@ protected function setUp() : void ->method('getDiffGenerator') ->willReturn($this->migrationDiffGenerator); + $this->dependencyFactory->expects(self::any()) + ->method('getMigrationStatusCalculator') + ->willReturn($this->migrationStatusCalculator); + $this->diffCommand = $this->getMockBuilder(DiffCommand::class) ->setConstructorArgs([$this->dependencyFactory]) ->onlyMethods(['procOpen'])