From 2def693a57308bfc6088d1b2d422def66964b090 Mon Sep 17 00:00:00 2001 From: Ondra Votava Date: Thu, 20 Sep 2018 08:24:58 +0200 Subject: [PATCH] Console: add command "migrations:status" issue #28 --- src/Bridges/NetteDI/MigrationsExtension.php | 5 + .../NextrasMigrationsExtension.php | 5 + src/Bridges/SymfonyConsole/StatusCommand.php | 36 +++++++ src/Engine/OrderResolver.php | 20 +++- src/Engine/Runner.php | 21 ++-- src/IPrinter.php | 28 ++++-- src/Printers/Console.php | 63 +++++++++--- src/Printers/DevNull.php | 45 ++++++--- src/Printers/HtmlDump.php | 95 ++++++++++++++----- .../integration/MigrationsExtension.phpt | 4 +- tests/cases/integration/Runner.FirstRun.phpt | 18 ++++ tests/cases/integration/Runner.SecondRun.phpt | 22 +++++ 12 files changed, 296 insertions(+), 66 deletions(-) create mode 100644 src/Bridges/SymfonyConsole/StatusCommand.php diff --git a/src/Bridges/NetteDI/MigrationsExtension.php b/src/Bridges/NetteDI/MigrationsExtension.php index 8503b88..3e21d1e 100644 --- a/src/Bridges/NetteDI/MigrationsExtension.php +++ b/src/Bridges/NetteDI/MigrationsExtension.php @@ -331,6 +331,11 @@ private function createSymfonyCommandDefinitions($driver, $configuration) ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand') ->setArguments([$driver, $configuration]) ->addTag('kdyby.console.command'); + + $builder->addDefinition($this->prefix('statusCommand')) + ->setClass('Nextras\Migrations\Bridges\SymfonyConsole\StatusCommand') + ->setArguments([$driver, $configuration]) + ->addTag('kdyby.console.command'); } } diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php index 71a578b..71a4ae9 100644 --- a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php +++ b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasMigrationsExtension.php @@ -81,12 +81,17 @@ public function load(array $configs, ContainerBuilder $container) $resetCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\ResetCommand'); $resetCommandDefinition->setAutowired(TRUE); $resetCommandDefinition->addTag('console.command'); + + $statusCommandDefinition = new Definition('Nextras\Migrations\Bridges\SymfonyConsole\StatusCommand'); + $statusCommandDefinition->setAutowired(TRUE); + $statusCommandDefinition->addTag('console.command'); $container->addDefinitions([ 'nextras_migrations.configuration' => $configurationDefinition, 'nextras_migrations.continue_command' => $continueCommandDefinition, 'nextras_migrations.create_command' => $createCommandDefinition, 'nextras_migrations.reset_command' => $resetCommandDefinition, + 'nextras_migrations.status_command' => $statusCommandDefinition, ]); if ($structureDiffGeneratorDefinition) { diff --git a/src/Bridges/SymfonyConsole/StatusCommand.php b/src/Bridges/SymfonyConsole/StatusCommand.php new file mode 100644 index 0000000..a7a88d9 --- /dev/null +++ b/src/Bridges/SymfonyConsole/StatusCommand.php @@ -0,0 +1,36 @@ +setName(self::$defaultName); + $this->setDescription('Show lists of completed or waiting migrations'); + } + + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->runMigrations(Runner::MODE_STATUS, $this->config); + } + +} + diff --git a/src/Engine/OrderResolver.php b/src/Engine/OrderResolver.php index 6e92656..2ffaa33 100644 --- a/src/Engine/OrderResolver.php +++ b/src/Engine/OrderResolver.php @@ -27,13 +27,13 @@ class OrderResolver */ public function resolve(array $migrations, array $groups, array $files, $mode) { + $this->checkModeSupport($mode); + $groups = $this->getAssocGroups($groups); $this->validateGroups($groups); if ($mode === Runner::MODE_RESET) { return $this->sortFiles($files, $groups); - } elseif ($mode !== Runner::MODE_CONTINUE) { - throw new LogicException('Unsupported mode.'); } $migrations = $this->getAssocMigrations($migrations); @@ -254,5 +254,19 @@ private function validateGroups(array $groups) } } } - + /** + * @param string $mode + */ + private function checkModeSupport($mode) + { + $supportedModes = [ + Runner::MODE_CONTINUE, + Runner::MODE_RESET, + Runner::MODE_STATUS, + ]; + + if (!in_array($mode, $supportedModes, true)) { + throw new LogicException('Unsupported mode.'); + } + } } diff --git a/src/Engine/Runner.php b/src/Engine/Runner.php index b8f56a3..bc12a68 100644 --- a/src/Engine/Runner.php +++ b/src/Engine/Runner.php @@ -29,6 +29,8 @@ class Runner const MODE_CONTINUE = 'continue'; const MODE_RESET = 'reset'; const MODE_INIT = 'init'; + const MODE_STATUS = 'status'; + /** @var IPrinter */ private $printer; @@ -82,9 +84,11 @@ public function addExtensionHandler($extension, IExtensionHandler $handler) /** - * @param string $mode self::MODE_CONTINUE|self::MODE_RESET|self::MODE_INIT + * @param string $mode self::MODE_CONTINUE|self::MODE_RESET|self::MODE_INIT|self::MODE_STATUS * @param IConfiguration $config + * * @return void + * @throws Exception */ public function run($mode = self::MODE_CONTINUE, IConfiguration $config = NULL) { @@ -120,12 +124,17 @@ public function run($mode = self::MODE_CONTINUE, IConfiguration $config = NULL) $migrations = $this->driver->getAllMigrations(); $files = $this->finder->find($this->groups, array_keys($this->extensionsHandlers)); $toExecute = $this->orderResolver->resolve($migrations, $this->groups, $files, $mode); - $this->printer->printToExecute($toExecute); - foreach ($toExecute as $file) { - $time = microtime(TRUE); - $queriesCount = $this->execute($file); - $this->printer->printExecute($file, $queriesCount, microtime(TRUE) - $time); + if ($mode === self::MODE_STATUS) { + $this->printer->printExecutedMigrations($migrations); + $this->printer->printToExecute($toExecute, true); + } else { + $this->printer->printToExecute($toExecute, false); + foreach ($toExecute as $file) { + $time = microtime(TRUE); + $queriesCount = $this->execute($file); + $this->printer->printExecute($file, $queriesCount, microtime(TRUE) - $time); + } } $this->driver->unlock(); diff --git a/src/IPrinter.php b/src/IPrinter.php index 586bc3c..a64d8f6 100644 --- a/src/IPrinter.php +++ b/src/IPrinter.php @@ -10,6 +10,7 @@ namespace Nextras\Migrations; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; /** @@ -23,14 +24,25 @@ interface IPrinter * - continue = Running new migrations. * @param string $mode */ - function printIntro($mode); - - + public function printIntro($mode); + + /** + * List of migrations which executed has been completed. + * @param Migration[] $migrations + * + * @return void + */ + public function printExecutedMigrations(array $migrations); + /** * List of migrations which should be executed has been completed. + * * @param File[] $toExecute + * @param bool $withFileList + * + * @return void */ - function printToExecute(array $toExecute); + public function printToExecute(array $toExecute, $withFileList = false); /** @@ -39,26 +51,26 @@ function printToExecute(array $toExecute); * @param int $count number of executed queries * @param float $time elapsed time in milliseconds */ - function printExecute(File $file, $count, $time); + public function printExecute(File $file, $count, $time); /** * All migrations have been successfully executed. */ - function printDone(); + public function printDone(); /** * An error has occurred during execution of a migration. * @param Exception $e */ - function printError(Exception $e); + public function printError(Exception $e); /** * Prints init source code. * @param string $code */ - function printSource($code); + public function printSource($code); } diff --git a/src/Printers/Console.php b/src/Printers/Console.php index 1ecf23d..00e4deb 100644 --- a/src/Printers/Console.php +++ b/src/Printers/Console.php @@ -10,6 +10,7 @@ namespace Nextras\Migrations\Printers; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; use Nextras\Migrations\Exception; use Nextras\Migrations\IPrinter; @@ -34,26 +35,60 @@ public function __construct() { $this->useColors = $this->detectColorSupport(); } - - + + /** + * @inheritdoc + */ public function printIntro($mode) { $this->output('Nextras Migrations'); $this->output(strtoupper($mode), self::COLOR_INTRO); } - - - public function printToExecute(array $toExecute) + + /** + * @inheritdoc + */ + public function printExecutedMigrations(array $migrations) + { + if ($migrations) { + $this->output('Executed migrations:'); + /** @var Migration $migration */ + foreach ($migrations as $migration) { + $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', self::COLOR_SUCCESS); + } + $this->output(' '); + } else { + $this->output('No migrations has executed yet.'); + } + } + + /** + * @inheritdoc + */ + public function printToExecute(array $toExecute, $withFileList = FALSE) { if ($toExecute) { $count = count($toExecute); - $this->output($count . ' migration' . ($count > 1 ? 's' : '') . ' need' . ($count > 1 ? '' : 's') . ' to be executed.'); + $this->output( + sprintf( + '%s migration%s need%s to be executed%s', + $count, $count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.') + ) + ); + if ($withFileList) { + /** @var File $file */ + foreach ($toExecute as $file) { + $this->output('- ' . $file->group->name . '/' . $file->name, self::COLOR_INFO); + } + } } else { $this->output('No migration needs to be executed.'); } } - - + + /** + * @inheritdoc + */ public function printExecute(File $file, $count, $time) { $this->output( @@ -62,14 +97,18 @@ public function printExecute(File $file, $count, $time) . $this->color(sprintf('%0.3f', $time), self::COLOR_INFO) . ' s' ); } - - + + /** + * @inheritdoc + */ public function printDone() { $this->output('OK', self::COLOR_SUCCESS); } - - + + /** + * @inheritdoc + */ public function printError(Exception $e) { $this->output('ERROR: ' . $e->getMessage(), self::COLOR_ERROR); diff --git a/src/Printers/DevNull.php b/src/Printers/DevNull.php index 5a26677..365b1e3 100644 --- a/src/Printers/DevNull.php +++ b/src/Printers/DevNull.php @@ -10,6 +10,7 @@ namespace Nextras\Migrations\Printers; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; use Nextras\Migrations\Exception; use Nextras\Migrations\IPrinter; @@ -20,32 +21,54 @@ */ class DevNull implements IPrinter { + /** + * @inheritdoc + */ public function printIntro($mode) { } - - - public function printToExecute(array $toExecute) + + /** + * @inheritdoc + */ + public function printToExecute(array $toExecute, $withFileList = false) { } - - + + /** + * @inheritdoc + */ public function printExecute(File $file, $count, $time) { } - - + + /** + * @inheritdoc + */ public function printDone() { } - - + + /** + * @inheritdoc + */ public function printError(Exception $e) { } - - + + /** + * @inheritdoc + */ public function printSource($code) { } + + /** + * @inheritdoc + */ + public function printExecutedMigrations(array $migrations) + { + + } } + diff --git a/src/Printers/HtmlDump.php b/src/Printers/HtmlDump.php index 0557578..6f7a9a7 100644 --- a/src/Printers/HtmlDump.php +++ b/src/Printers/HtmlDump.php @@ -11,6 +11,7 @@ use Nextras\Migrations\Engine\Runner; use Nextras\Migrations\Entities\File; +use Nextras\Migrations\Entities\Migration; use Nextras\Migrations\Exception; use Nextras\Migrations\IPrinter; @@ -25,69 +26,115 @@ class HtmlDump implements IPrinter /** @var int order of last executed migration */ private $index; - - + + /** + * @inheritdoc + */ public function printIntro($mode) { if ($mode === Runner::MODE_RESET) { $this->output(' RESET: All tables, views and data has been destroyed!'); - } else { + } + if ($mode === Runner::MODE_CONTINUE) { $this->output(' CONTINUE: Running only new migrations.'); } + if ($mode === Runner::MODE_STATUS) { + $this->output(' STATUS: Show lists of completed or waiting migrations'); + } } - - - public function printToExecute(array $toExecute) + + public function printExecutedMigrations(array $migrations) { + if ($migrations) { + $this->output('Executed migrations:'); + /** @var Migration $migration */ + foreach ($migrations as $migration) { + $this->output('- ' . $migration->group . '/' . $migration->filename . ' OK', 'success'); + } + $this->output(' '); + } else { + $this->output('No migrations has executed yet'); + } + } + + /** + * @inheritdoc + */ + public function printToExecute(array $toExecute, $withFileList = FALSE) + { + $count = 0; if ($toExecute) { - $this->output(' ' . count($toExecute) . ' migrations need to be executed.'); + $count = count($toExecute); + $this->output( + sprintf( + '%s migration%s need%s to be executed%s', + $count, $count > 1 ? 's' : '', $count > 1 ? '' : 's', ($withFileList ? ':' : '.') + ) + ); + if ($withFileList) { + /** @var File $file */ + foreach ($toExecute as $file) { + $this->output(' - ' . $file->group->name . '/' . $file->name); + } + } } else { $this->output('No migration needs to be executed.'); } - $this->count = count($toExecute); + $this->count = $count; $this->index = 0; } - - + + /** + * @inheritdoc + */ public function printExecute(File $file, $count, $time) { $format = '%0' . strlen($this->count) . 'd'; $name = htmlspecialchars($file->group->name . '/' . $file->name); - $this->output(sprintf( - $format . '/' . $format . ': %s (%d %s, %0.3f s)', - ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time - )); + $this->output( + sprintf( + $format . '/' . $format . ': %s (%d %s, %0.3f s)', + ++$this->index, $this->count, $name, $count, ($count === 1 ? 'query' : 'queries'), $time + ) + ); } - - + + /** + * @inheritdoc + */ public function printDone() { $this->output('OK', 'success'); } - - + + /** + * @inheritdoc + */ public function printError(Exception $e) { $this->output('ERROR: ' . htmlspecialchars($e->getMessage()), 'error'); throw $e; } - - + + /** + * @inheritdoc + */ public function printSource($code) { $this->output($code); } - - + + /** - * @param string $s HTML string + * @param string $s HTML string * @param string $class + * * @return void */ protected function output($s, $class = 'info') { echo "
$s
\n"; } - + } diff --git a/tests/cases/integration/MigrationsExtension.phpt b/tests/cases/integration/MigrationsExtension.phpt index 964b6e4..f81aa26 100644 --- a/tests/cases/integration/MigrationsExtension.phpt +++ b/tests/cases/integration/MigrationsExtension.phpt @@ -25,8 +25,8 @@ class MigrationsExtensionTest extends TestCase $dic = $this->createContainer($config); Assert::type('Nextras\Migrations\Drivers\MySqlDriver', $dic->getByType('Nextras\Migrations\IDriver')); - Assert::count(3, $dic->findByType('Symfony\Component\Console\Command\Command')); - Assert::count(3, $dic->findByTag('kdyby.console.command')); + Assert::count(4, $dic->findByType('Symfony\Component\Console\Command\Command')); + Assert::count(4, $dic->findByTag('kdyby.console.command')); } diff --git a/tests/cases/integration/Runner.FirstRun.phpt b/tests/cases/integration/Runner.FirstRun.phpt index c683ec9..54f02b4 100644 --- a/tests/cases/integration/Runner.FirstRun.phpt +++ b/tests/cases/integration/Runner.FirstRun.phpt @@ -110,6 +110,24 @@ class FirstRunTest extends IntegrationTestCase } } } + + public function testStatus() + { + $this->runner->run(Runner::MODE_STATUS); + Assert::same([ + 'Nextras Migrations', + 'STATUS', + 'No migrations has executed yet.', + '5 migrations need to be executed:', + '- structures/001.sql', + '- structures/002.sql', + '- basic-data/003.sql', + '- dummy-data/004.sql', + '- structures/005.sql', + 'OK', + ], $this->printer->lines); + + } } diff --git a/tests/cases/integration/Runner.SecondRun.phpt b/tests/cases/integration/Runner.SecondRun.phpt index bacb130..be6d21f 100644 --- a/tests/cases/integration/Runner.SecondRun.phpt +++ b/tests/cases/integration/Runner.SecondRun.phpt @@ -95,6 +95,28 @@ class SecondRunTest extends IntegrationTestCase } } } + + public function testStatus() + { + $this->driver->loadFile($this->fixtureDir . '/3ok.sql'); + Assert::count(3, $this->driver->getAllMigrations()); + + $this->runner->run(Runner::MODE_STATUS); + Assert::same([ + 'Nextras Migrations', + 'STATUS', + 'Executed migrations:', + '- structures/001.sql OK', + '- structures/002.sql OK', + '- basic-data/003.sql OK', + ' ', + '2 migrations need to be executed:', + '- dummy-data/004.sql', + '- structures/005.sql', + 'OK', + ], $this->printer->lines); + + } }