diff --git a/composer.json b/composer.json index 24b9332..efc67a1 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "require": { "symfony/console": "^2.7", "psr/log": "^1.0", - "baleen/migrations": "^0.5", + "baleen/migrations": "^0.6", "symfony/yaml": "^2.7", "symfony/config": "^2.7", "league/container": "^1.3", diff --git a/src/Command/Timeline/AbstractTimelineCommand.php b/src/Command/Timeline/AbstractTimelineCommand.php index a53b1f8..7353896 100644 --- a/src/Command/Timeline/AbstractTimelineCommand.php +++ b/src/Command/Timeline/AbstractTimelineCommand.php @@ -21,6 +21,7 @@ namespace Baleen\Cli\Command\Timeline; use Baleen\Cli\Command\AbstractCommand; +use Baleen\Migrations\Storage\StorageInterface; use Baleen\Migrations\Timeline; use Symfony\Component\Console\Input\InputOption; @@ -33,17 +34,27 @@ abstract class AbstractTimelineCommand extends AbstractCommand { const COMMAND_GROUP = 'timeline'; const OPT_DRY_RUN = 'dry-run'; + const OPT_NO_STORAGE = 'no-storage'; /** @var Timeline */ protected $timeline; + /** @var StorageInterface */ + protected $storage; + /** * @inheritDoc */ public function configure() { parent::configure(); - $this->addOption(self::OPT_DRY_RUN, 'd', InputOption::VALUE_NONE, 'Execute the migration on dry-run mode.'); + $this->addOption(self::OPT_DRY_RUN, 'd', InputOption::VALUE_NONE, 'Execute the migration on dry-run mode.') + ->addOption( + self::OPT_NO_STORAGE, + null, + InputOption::VALUE_NONE, + 'Do not persist execution results to storage.' + ); } /** @@ -61,4 +72,20 @@ public function setTimeline(Timeline $timeline) { $this->timeline = $timeline; } + + /** + * @return StorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * @param StorageInterface $storage + */ + public function setStorage($storage) + { + $this->storage = $storage; + } } diff --git a/src/Command/Timeline/ExecuteCommand.php b/src/Command/Timeline/ExecuteCommand.php index 9a8121c..cb17187 100644 --- a/src/Command/Timeline/ExecuteCommand.php +++ b/src/Command/Timeline/ExecuteCommand.php @@ -82,7 +82,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $canExecute = $this->getHelper('question')->ask($input, $output, new ConfirmationQuestion($question)); } if ($canExecute) { - $this->getTimeline()->runSingle($version, $options); + $result = $this->getTimeline()->runSingle($version, $options); + if ($result) { + $version = $result; + $this->getStorage()->update($version); + } $output->writeln("Version {$version->getId()} migrated $direction successfully."); } } diff --git a/src/Command/Timeline/MigrateCommand.php b/src/Command/Timeline/MigrateCommand.php index 16963aa..9133c26 100644 --- a/src/Command/Timeline/MigrateCommand.php +++ b/src/Command/Timeline/MigrateCommand.php @@ -26,6 +26,8 @@ use Baleen\Migrations\Event\Timeline\MigrationEvent; use Baleen\Migrations\Migration\Options; use Baleen\Migrations\Timeline; +use Baleen\Migrations\Version; +use Baleen\Migrations\Version\Collection\MigratedVersions; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -51,6 +53,9 @@ class MigrateCommand extends AbstractTimelineCommand /** @var ProgressBar */ protected $progress; + /** @var bool */ + protected $saveChanges = true; + /** @var array */ protected $strategies = [ Options::DIRECTION_UP => 'upTowards', @@ -61,6 +66,9 @@ class MigrateCommand extends AbstractTimelineCommand /** @var bool */ protected $trackProgress = true; + /** @var string */ + protected $directionPhrase = 'Migrating to'; + /** * @inheritdoc */ @@ -91,16 +99,19 @@ public function configure() protected function execute(InputInterface $input, OutputInterface $output) { $targetArg = $input->getArgument(self::ARG_TARGET); + $this->saveChanges = !$input->getOption(self::OPT_NO_STORAGE); $strategy = $this->getStrategyOption($input); $options = new Options(Options::DIRECTION_UP); // this value will get replaced by timeline later $options->setDryRun($input->getOption(self::OPT_DRY_RUN)); + $options->setExceptionOnSkip(false); $this->trackProgress = ($output->getVerbosity() !== OutputInterface::VERBOSITY_QUIET) && !$input->getOption(self::OPT_NOPROGRESS); $this->attachEvents($output); + /** @var Version\Collection\LinkedVersions $results */ $this->getTimeline()->$strategy($targetArg, $options); } @@ -118,6 +129,20 @@ protected function attachEvents(OutputInterface $output) $dispatcher->addListener(EventInterface::MIGRATION_BEFORE, [$this, 'onMigrationBefore']); $dispatcher->addListener(EventInterface::MIGRATION_AFTER, [$this, 'onMigrationAfter']); } + + if ($this->saveChanges) { + $dispatcher->addListener(EventInterface::MIGRATION_AFTER, [$this, 'saveVersionListener']); + } + } + + /** + * saveVersionListener + * @param MigrationEvent $event + */ + public function saveVersionListener(MigrationEvent $event) + { + $version = $event->getVersion(); + $this->getStorage()->update($version); } /** @@ -155,8 +180,10 @@ public function onMigrationAfter(MigrationEvent $event) public function onCollectionBefore(CollectionEvent $event) { $target = $event->getTarget(); + $this->output->writeln(sprintf( - '[START] Migrating towards %s:', + '[START] Migrating %s to %s:', + $event->getOptions()->isDirectionUp() ? 'up' : 'down', $target->getId() )); if ($this->trackProgress) { @@ -175,7 +202,7 @@ public function onCollectionAfter() $this->progress->finish(); $this->output->writeln(''); // new line after progress bar } - $this->output->writeln('[END] All done!'); + $this->output->writeln('[END]'); } /** diff --git a/src/Container/ServiceProvider/DefaultProvider.php b/src/Container/ServiceProvider/DefaultProvider.php index 92d2f27..e24c1f6 100644 --- a/src/Container/ServiceProvider/DefaultProvider.php +++ b/src/Container/ServiceProvider/DefaultProvider.php @@ -78,7 +78,8 @@ public function register() ->invokeMethod('setStorage', [StorageProvider::SERVICE_STORAGE]); $container->inflector(AbstractTimelineCommand::class) - ->invokeMethod('setTimeline', [TimelineProvider::SERVICE_TIMELINE]); + ->invokeMethod('setTimeline', [TimelineProvider::SERVICE_TIMELINE]) + ->invokeMethod('setStorage', [StorageProvider::SERVICE_STORAGE]); $container->inflector(InitCommand::class) ->invokeMethod('setConfigStorage', [AppConfigProvider::SERVICE_CONFIG_STORAGE]); diff --git a/test/Command/Timeline/MigrateCommandTest.php b/test/Command/Timeline/MigrateCommandTest.php index 8ee3b83..a9d783f 100644 --- a/test/Command/Timeline/MigrateCommandTest.php +++ b/test/Command/Timeline/MigrateCommandTest.php @@ -69,9 +69,10 @@ public function testConfigure() * testExecute * @param $verbosity * @param $noProgress + * @param $noStorage * @dataProvider executeProvider */ - public function testExecute($verbosity, $noProgress) + public function testExecute($verbosity, $noProgress, $noStorage) { // values don't matter here $strategy = 'both'; @@ -90,6 +91,7 @@ public function testExecute($verbosity, $noProgress) $this->input->shouldReceive('getArgument')->with(MigrateCommand::ARG_TARGET)->once()->andReturn($target); $this->input->shouldReceive('getOption')->with(MigrateCommand::OPT_DRY_RUN)->once()->andReturn($dryRun); + $this->input->shouldReceive('getOption')->with(MigrateCommand::OPT_NO_STORAGE)->once()->andReturn($noStorage); $this->instance->shouldReceive('getStrategyOption')->with($this->input)->andReturn($strategy); $this->instance->shouldReceive('attachEvents')->once()->with($this->output); $this->instance->shouldReceive('getTimeline->' . $strategy)->once()->with($target, m::type(Options::class)); @@ -97,6 +99,7 @@ public function testExecute($verbosity, $noProgress) $this->execute(); $this->assertEquals($shouldTrackProgress, $this->getPropVal('trackProgress', $this->instance)); + $this->assertEquals(!$noStorage, $this->getPropVal('saveChanges', $this->instance)); } /** @@ -112,7 +115,8 @@ public function executeProvider() OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG, ]; - return $this->combinations([$verbosities, [true, false]]); + $trueFalse = [true, false]; + return $this->combinations([$verbosities, $trueFalse, $trueFalse]); } /** @@ -149,7 +153,7 @@ public function getStrategyOptionProvider() */ public function testOnCollectionAfter() { - $this->output->shouldReceive('writeln')->with('/done/')->once(); + $this->output->shouldReceive('writeln')->with('/END/')->once(); $this->setPropVal('output', $this->output, $this->instance); $this->invokeMethod('onCollectionAfter', $this->instance); } @@ -157,13 +161,18 @@ public function testOnCollectionAfter() /** * testOnCollectionBefore * @param bool $trackProgress + * @param bool $isDirectionUp + * @dataProvider onCollectionBeforeProvider */ - public function testOnCollectionBefore($trackProgress = true) + public function testOnCollectionBefore($trackProgress = true, $isDirectionUp = true) { $target = new Version('v10'); /** @var m\Mock|CollectionEvent $event */ $event = m::mock(CollectionEvent::class); - $event->shouldReceive(['getTarget' => $target])->once(); + $event->shouldReceive([ + 'getTarget' => $target, + 'getOptions->isDirectionUp' => $isDirectionUp, + ])->once(); $this->output->shouldReceive('writeln')->with('/' . $target->getId() . '/')->once(); $this->setPropVal('output', $this->output, $this->instance); @@ -182,11 +191,14 @@ public function testOnCollectionBefore($trackProgress = true) $this->invokeMethod('onCollectionBefore', $this->instance, [$event]); } - public function trackProgressProvider() + /** + * onCollectionBeforeProvider + * @return array + */ + public function onCollectionBeforeProvider() { - return [ - [true], [false] - ]; + $trueFalse = [true, false]; + return $this->combinations([$trueFalse, $trueFalse]); } /** @@ -205,17 +217,53 @@ public function testOnMigrationBefore() $this->invokeMethod('onMigrationBefore', $this->instance, [$event]); } - public function testAttachEvents($verbosity = 1) + /** + * testAttachEvents + * @param int $verbosity + * @param $saveChanges + * @dataProvider attachEventsProvider + */ + public function testAttachEvents($verbosity, $saveChanges) { $dispatcher = m::mock(EventDispatcher::class); $this->instance->shouldReceive('getTimeline->getEventDispatcher')->once()->andReturn($dispatcher); + $this->setPropVal('saveChanges', $saveChanges, $this->instance); $this->output->shouldReceive('getVerbosity')->andReturn($verbosity); + $counts = [ + EventInterface::MIGRATION_BEFORE => 0, + EventInterface::MIGRATION_AFTER => 0, + EventInterface::COLLECTION_BEFORE => 0, + EventInterface::COLLECTION_AFTER => 0, + ]; if ($verbosity >= OutputInterface::VERBOSITY_NORMAL) { - $dispatcher->shouldReceive('addListener')->once()->with(EventInterface::MIGRATION_BEFORE, m::any()); - $dispatcher->shouldReceive('addListener')->once()->with(EventInterface::MIGRATION_AFTER, m::any()); - $dispatcher->shouldReceive('addListener')->once()->with(EventInterface::COLLECTION_BEFORE, m::any()); - $dispatcher->shouldReceive('addListener')->once()->with(EventInterface::COLLECTION_AFTER, m::any()); + $counts = [ + EventInterface::MIGRATION_BEFORE => 1, + EventInterface::MIGRATION_AFTER => 1, + EventInterface::COLLECTION_BEFORE => 1, + EventInterface::COLLECTION_AFTER => 1, + ]; + } + if ($saveChanges) { + $counts[EventInterface::MIGRATION_AFTER] += 1; + } + foreach ($counts as $event => $count) { + $dispatcher->shouldReceive('addListener')->times($count)->with($event, m::any()); } $this->invokeMethod('attachEvents', $this->instance, [$this->output]); } + + /** + * attachEventsProvider + * @return array + */ + public function attachEventsProvider() + { + $trueFalse = [true, false]; + $verbosities = [ + OutputInterface::VERBOSITY_QUIET, + OutputInterface::OUTPUT_NORMAL, + OutputInterface::VERBOSITY_VERY_VERBOSE, + ]; + return $this->combinations([$verbosities, $trueFalse]); + } }