From c7474aff14f0fcfc08579343f811741ddd4455a4 Mon Sep 17 00:00:00 2001 From: Maksim Vorozhtsov Date: Sat, 9 Mar 2024 18:56:57 +0300 Subject: [PATCH] Ask for a namespace of when more one path --- src/Tools/Console/Command/DiffCommand.php | 19 +- src/Tools/Console/Command/DoctrineCommand.php | 38 ++++ .../Console/Command/DumpSchemaCommand.php | 13 +- src/Tools/Console/Command/GenerateCommand.php | 20 +- .../Tools/Console/Command/DiffCommandTest.php | 41 ++++ .../Console/Command/DoctrineCommandTest.php | 175 ++++++++++++++++++ .../Console/Command/DumpSchemaCommandTest.php | 57 +++++- .../Console/Command/GenerateCommandTest.php | 51 ++++- 8 files changed, 345 insertions(+), 69 deletions(-) diff --git a/src/Tools/Console/Command/DiffCommand.php b/src/Tools/Console/Command/DiffCommand.php index 6ec790128..3bebfc578 100644 --- a/src/Tools/Console/Command/DiffCommand.php +++ b/src/Tools/Console/Command/DiffCommand.php @@ -9,19 +9,15 @@ use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Doctrine\SqlFormatter\SqlFormatter; -use OutOfBoundsException; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function addslashes; -use function assert; use function class_exists; use function count; use function filter_var; -use function is_string; -use function key; use function sprintf; use const FILTER_VALIDATE_BOOLEAN; @@ -110,10 +106,6 @@ protected function execute( $allowEmptyDiff = $input->getOption('allow-empty-diff'); $checkDbPlatform = filter_var($input->getOption('check-database-platform'), FILTER_VALIDATE_BOOLEAN); $fromEmptySchema = $input->getOption('from-empty-schema'); - $namespace = $input->getOption('namespace'); - if ($namespace === '') { - $namespace = null; - } if ($formatted) { if (! class_exists(SqlFormatter::class)) { @@ -123,16 +115,7 @@ protected function execute( } } - $configuration = $this->getDependencyFactory()->getConfiguration(); - - $dirs = $configuration->getMigrationDirectories(); - if ($namespace === null) { - $namespace = key($dirs); - } elseif (! isset($dirs[$namespace])) { - throw new OutOfBoundsException(sprintf('Path not defined for the namespace %s', $namespace)); - } - - assert(is_string($namespace)); + $namespace = $this->getNamespace($input, $output); $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); diff --git a/src/Tools/Console/Command/DoctrineCommand.php b/src/Tools/Console/Command/DoctrineCommand.php index cc908ec88..661f30d54 100644 --- a/src/Tools/Console/Command/DoctrineCommand.php +++ b/src/Tools/Console/Command/DoctrineCommand.php @@ -10,16 +10,22 @@ use Doctrine\Migrations\Tools\Console\ConsoleLogger; use Doctrine\Migrations\Tools\Console\Exception\DependenciesNotSatisfied; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; +use Exception; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use function array_keys; use function assert; +use function count; use function is_string; +use function key; +use function sprintf; /** * The DoctrineCommand class provides base functionality for the other migrations commands to extend from. @@ -138,4 +144,36 @@ private function setNamedEmOrConnection(InputInterface $input): void return; } } + + final protected function getNamespace(InputInterface $input, OutputInterface $output): string + { + $configuration = $this->getDependencyFactory()->getConfiguration(); + + $namespace = $input->getOption('namespace'); + if ($namespace === '') { + $namespace = null; + } + + $dirs = $configuration->getMigrationDirectories(); + if ($namespace === null && count($dirs) === 1) { + $namespace = key($dirs); + } elseif ($namespace === null && count($dirs) > 1) { + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please choose a namespace (defaults to the first one)', + array_keys($dirs), + 0, + ); + $namespace = $helper->ask($input, $output, $question); + $this->io->text(sprintf('You have selected the "%s" namespace', $namespace)); + } + + if (! isset($dirs[$namespace])) { + throw new Exception(sprintf('Path not defined for the namespace "%s"', $namespace)); + } + + assert(is_string($namespace)); + + return $namespace; + } } diff --git a/src/Tools/Console/Command/DumpSchemaCommand.php b/src/Tools/Console/Command/DumpSchemaCommand.php index dd927db82..724d0cf34 100644 --- a/src/Tools/Console/Command/DumpSchemaCommand.php +++ b/src/Tools/Console/Command/DumpSchemaCommand.php @@ -13,10 +13,7 @@ use Symfony\Component\Console\Output\OutputInterface; use function addslashes; -use function assert; use function class_exists; -use function is_string; -use function key; use function sprintf; use function str_contains; @@ -91,15 +88,7 @@ public function execute( } } - $configuration = $this->getDependencyFactory()->getConfiguration(); - - $namespace = $input->getOption('namespace'); - if ($namespace === null) { - $dirs = $configuration->getMigrationDirectories(); - $namespace = key($dirs); - } - - assert(is_string($namespace)); + $namespace = $this->getNamespace($input, $output); $this->checkNoPreviousDumpExistsForNamespace($namespace); diff --git a/src/Tools/Console/Command/GenerateCommand.php b/src/Tools/Console/Command/GenerateCommand.php index 3bddf9e4e..043d6485e 100644 --- a/src/Tools/Console/Command/GenerateCommand.php +++ b/src/Tools/Console/Command/GenerateCommand.php @@ -4,15 +4,11 @@ namespace Doctrine\Migrations\Tools\Console\Command; -use Exception; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use function assert; -use function is_string; -use function key; use function sprintf; /** @@ -47,23 +43,9 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $configuration = $this->getDependencyFactory()->getConfiguration(); - $migrationGenerator = $this->getDependencyFactory()->getMigrationGenerator(); - $namespace = $input->getOption('namespace'); - if ($namespace === '') { - $namespace = null; - } - - $dirs = $configuration->getMigrationDirectories(); - if ($namespace === null) { - $namespace = key($dirs); - } elseif (! isset($dirs[$namespace])) { - throw new Exception(sprintf('Path not defined for the namespace %s', $namespace)); - } - - assert(is_string($namespace)); + $namespace = $this->getNamespace($input, $output); $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); diff --git a/tests/Tools/Console/Command/DiffCommandTest.php b/tests/Tools/Console/Command/DiffCommandTest.php index 366f27e3f..c568e1b3f 100644 --- a/tests/Tools/Console/Command/DiffCommandTest.php +++ b/tests/Tools/Console/Command/DiffCommandTest.php @@ -18,10 +18,13 @@ use Doctrine\Migrations\Version\Version; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use function array_map; use function explode; +use function sprintf; use function sys_get_temp_dir; use function trim; @@ -139,6 +142,44 @@ public function testExecutedUnavailableMigrationsCancel(): void self::assertSame(3, $statusCode); } + /** @return array */ + public static function getSelectedNamespace(): array + { + return [ + 'no' => [null, 'FooNs'], + 'first' => [0, 'FooNs'], + 'two' => [1, 'FooNs2'], + ]; + } + + /** @dataProvider getSelectedNamespace */ + public function testExecuteWithMultipleDirectories(int|null $input, string $namespace): void + { + $this->migrationStatusCalculator + ->method('getNewMigrations') + ->willReturn(new AvailableMigrationsList([])); + + $this->migrationStatusCalculator + ->method('getExecutedUnavailableMigrations') + ->willReturn(new ExecutedMigrationsList([])); + + $this->configuration->addMigrationsDirectory('FooNs2', sys_get_temp_dir()); + + $this->diffCommand->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $this->migrationDiffGenerator->expects(self::once())->method('generate'); + + $this->diffCommandTester->setInputs([$input]); + $this->diffCommandTester->execute([]); + + $output = $this->diffCommandTester->getDisplay(true); + + self::assertStringContainsString('Please choose a namespace (defaults to the first one)', $output); + self::assertStringContainsString('[0] FooNs', $output); + self::assertStringContainsString('[1] FooNs2', $output); + self::assertStringContainsString(sprintf('You have selected the "%s" namespace', $namespace), $output); + } + protected function setUp(): void { $this->migrationDiffGenerator = $this->createMock(DiffGenerator::class); diff --git a/tests/Tools/Console/Command/DoctrineCommandTest.php b/tests/Tools/Console/Command/DoctrineCommandTest.php index 9b9520d8e..d192644a9 100644 --- a/tests/Tools/Console/Command/DoctrineCommandTest.php +++ b/tests/Tools/Console/Command/DoctrineCommandTest.php @@ -16,7 +16,10 @@ use Doctrine\Migrations\Tools\Console\Command\DoctrineCommand; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Doctrine\ORM\EntityManager; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -207,4 +210,176 @@ protected function execute(InputInterface $input, OutputInterface $output): int ['interactive' => false], ); } + + public function testNamespaceFromOption(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrations', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + DoctrineCommandTest::assertSame($this->getNamespace($input, $output), 'DoctrineMigrations'); + + return DoctrineCommand::SUCCESS; + } + }; + + $commandTester = new CommandTester($command); + $commandTester->execute( + ['--namespace' => 'DoctrineMigrations'], + ['interactive' => false], + ); + } + + public function testNamespaceUnknown(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrations', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->getNamespace($input, $output); + + return DoctrineCommand::SUCCESS; + } + }; + + $commandTester = new CommandTester($command); + + $this->expectExceptionMessage('Path not defined for the namespace "Unknown"'); + $commandTester->execute( + ['--namespace' => 'Unknown'], + ['interactive' => false], + ); + } + + public function testNamespaceFirstFromDirectories(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrations', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + DoctrineCommandTest::assertSame($this->getNamespace($input, $output), 'DoctrineMigrations'); + + return DoctrineCommand::SUCCESS; + } + }; + + $commandTester = new CommandTester($command); + $commandTester->execute([]); + } + + public function testNamespaceChoiceTwoDirectories(): void + { + $configuration = new Configuration(); + $configuration->addMigrationsDirectory('DoctrineMigrationsFirst', sys_get_temp_dir()); + $configuration->addMigrationsDirectory('DoctrineMigrationsTwo', sys_get_temp_dir()); + + $conn = $this->createMock(Connection::class); + $connLoader = new ExistingConnection($conn); + + $dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + $connLoader, + ); + + $command = new class ($dependencyFactory) extends DoctrineCommand + { + protected function configure(): void + { + parent::configure(); + + $this + ->addOption( + 'namespace', + null, + InputOption::VALUE_REQUIRED, + 'The namespace to use for the migration (must be in the list of configured namespaces)', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + DoctrineCommandTest::assertSame($this->getNamespace($input, $output), 'DoctrineMigrationsTwo'); + + return DoctrineCommand::SUCCESS; + } + }; + + $command->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $commandTester = new CommandTester($command); + $commandTester->setInputs([1]); + $commandTester->execute([]); + } } diff --git a/tests/Tools/Console/Command/DumpSchemaCommandTest.php b/tests/Tools/Console/Command/DumpSchemaCommandTest.php index e1d682c65..b37fca641 100644 --- a/tests/Tools/Console/Command/DumpSchemaCommandTest.php +++ b/tests/Tools/Console/Command/DumpSchemaCommandTest.php @@ -18,10 +18,13 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use function array_map; use function explode; +use function sprintf; use function sys_get_temp_dir; use function trim; @@ -62,6 +65,16 @@ public function testExecute(): void ->method('getMigrations') ->willReturn(new AvailableMigrationsSet([])); + $classNameGenerator = $this->createMock(ClassNameGenerator::class); + $classNameGenerator + ->method('generateClassName') + ->with('FooNs') + ->willReturn('FooNs\\Version1234'); + + $this->dependencyFactory->expects(self::any()) + ->method('getClassNameGenerator') + ->willReturn($classNameGenerator); + $this->schemaDumper->expects(self::once()) ->method('dump') ->with('FooNs\\Version1234', ['/foo/'], true, 80); @@ -88,6 +101,40 @@ public function testExecute(): void ); } + /** @return array> */ + public static function getNamespaceSelected(): array + { + return [ + 'no' => [null, 'FooNs'], + 'first' => [0, 'FooNs'], + 'two' => [1, 'FooNs2'], + ]; + } + + /** @dataProvider getNamespaceSelected */ + public function testExecuteWithMultipleDirectories(int|null $input, string $namespace): void + { + $this->migrationRepository->expects(self::once()) + ->method('getMigrations') + ->willReturn(new AvailableMigrationsSet([])); + + $this->configuration->addMigrationsDirectory('FooNs2', sys_get_temp_dir()); + + $this->dumpSchemaCommand->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $this->schemaDumper->expects(self::once())->method('dump'); + + $this->dumpSchemaCommandTester->setInputs([$input]); + $this->dumpSchemaCommandTester->execute([]); + + $output = $this->dumpSchemaCommandTester->getDisplay(true); + + self::assertStringContainsString('Please choose a namespace (defaults to the first one)', $output); + self::assertStringContainsString('[0] FooNs', $output); + self::assertStringContainsString('[1] FooNs2', $output); + self::assertStringContainsString(sprintf('You have selected the "%s" namespace', $namespace), $output); + } + protected function setUp(): void { $this->configuration = new Configuration(); @@ -97,16 +144,6 @@ protected function setUp(): void $this->migrationRepository = $this->createMock(FilesystemMigrationsRepository::class); $this->schemaDumper = $this->createMock(SchemaDumper::class); - $classNameGenerator = $this->createMock(ClassNameGenerator::class); - $classNameGenerator->expects(self::any()) - ->method('generateClassName') - ->with('FooNs') - ->willReturn('FooNs\\Version1234'); - - $this->dependencyFactory->expects(self::any()) - ->method('getClassNameGenerator') - ->willReturn($classNameGenerator); - $this->dependencyFactory->expects(self::any()) ->method('getSchemaDumper') ->willReturn($this->schemaDumper); diff --git a/tests/Tools/Console/Command/GenerateCommandTest.php b/tests/Tools/Console/Command/GenerateCommandTest.php index 6f8a05a01..27cba6a37 100644 --- a/tests/Tools/Console/Command/GenerateCommandTest.php +++ b/tests/Tools/Console/Command/GenerateCommandTest.php @@ -11,10 +11,13 @@ use Doctrine\Migrations\Tools\Console\Command\GenerateCommand; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use function array_map; use function explode; +use function sprintf; use function sys_get_temp_dir; use function trim; @@ -39,6 +42,16 @@ public function testExecute(): void ->with('FooNs\\Version1234') ->willReturn('/path/to/migration.php'); + $classNameGenerator = $this->createMock(ClassNameGenerator::class); + $classNameGenerator->expects(self::once()) + ->method('generateClassName') + ->with('FooNs') + ->willReturn('FooNs\\Version1234'); + + $this->dependencyFactory->expects(self::once()) + ->method('getClassNameGenerator') + ->willReturn($classNameGenerator); + $this->generateCommandTest->execute([]); $output = $this->generateCommandTest->getDisplay(true); @@ -51,6 +64,34 @@ public function testExecute(): void ], array_map(trim(...), explode("\n", trim($output)))); } + /** @return array> */ + public static function getNamespaceSelected(): array + { + return [ + 'no' => [null, 'FooNs'], + 'first' => [0, 'FooNs'], + 'two' => [1, 'FooNs2'], + ]; + } + + /** @dataProvider getNamespaceSelected */ + public function testExecuteWithMultipleDirectories(int|null $input, string $namespace): void + { + $this->configuration->addMigrationsDirectory('FooNs2', sys_get_temp_dir()); + + $this->generateCommand->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); + + $this->generateCommandTest->setInputs([$input]); + $this->generateCommandTest->execute([]); + + $output = $this->generateCommandTest->getDisplay(true); + + self::assertStringContainsString('Please choose a namespace (defaults to the first one)', $output); + self::assertStringContainsString('[0] FooNs', $output); + self::assertStringContainsString('[1] FooNs2', $output); + self::assertStringContainsString(sprintf('You have selected the "%s" namespace', $namespace), $output); + } + protected function setUp(): void { $this->configuration = new Configuration(); @@ -59,16 +100,6 @@ protected function setUp(): void $this->dependencyFactory = $this->createMock(DependencyFactory::class); $this->migrationGenerator = $this->createMock(Generator::class); - $classNameGenerator = $this->createMock(ClassNameGenerator::class); - $classNameGenerator->expects(self::once()) - ->method('generateClassName') - ->with('FooNs') - ->willReturn('FooNs\\Version1234'); - - $this->dependencyFactory->expects(self::once()) - ->method('getClassNameGenerator') - ->willReturn($classNameGenerator); - $this->dependencyFactory->expects(self::any()) ->method('getConfiguration') ->willReturn($this->configuration);