setDescription('View the status of all (or a set of) migrations.') ->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from") ->addOption('sort-by', null, InputOption::VALUE_REQUIRED, "Supported sorting order: name, execution", 'name') ->addOption('summary', null, InputOption::VALUE_NONE, "Only print summary information") ->addOption('todo', null, InputOption::VALUE_NONE, "Only print list of migrations to execute (full path to each)") ->addOption('show-path', null, InputOption::VALUE_NONE, "Print migration path instead of status") ->setHelp(<<kaliop:migration:status command displays the status of all available migrations: ./ezpublish/console kaliop:migration:status You can optionally specify the path to migration versions with --path: ./ezpublish/console kaliop:migrations:status --path=/path/to/bundle/version_directory --path=/path/to/bundle/version_directory/single_migration_file EOT ); } public function execute(InputInterface $input, OutputInterface $output) { $this->setOutput($output); $this->setVerbosity($output->getVerbosity()); $migrationsService = $this->getMigrationService(); $displayPath = $input->getOption('show-path'); $migrationDefinitions = $migrationsService->getMigrationsDefinitions($input->getOption('path')); $migrations = $migrationsService->getMigrationsByPaths($input->getOption('path')); if (!count($migrationDefinitions) && !count($migrations) && !$input->getOption('summary')) { $output->writeln('No migrations found'); return 0; } // create a unique list of all migrations (coming from db) and definitions (coming from disk) $index = array(); foreach ($migrationDefinitions as $migrationDefinition) { $index[$migrationDefinition->name] = array('definition' => $migrationDefinition); } foreach ($migrations as $migration) { if (isset($index[$migration->name])) { $index[$migration->name]['migration'] = $migration; } else { $index[$migration->name] = array('migration' => $migration); // no definition, but a migration is there. Check if the definition sits elsewhere on disk than we expect it to be... // q: what if we have a loader which does not work with is_file? Could we remove this check? if ($migration->path != '' && is_file($migration->path)) { try { $migrationDefinitionCollection = $migrationsService->getMigrationsDefinitions(array($migration->path)); if (count($migrationDefinitionCollection)) { $index[$migration->name]['definition'] = $migrationDefinitionCollection->reset(); } } catch (\Exception $e) { /// @todo one day we should be able to limit the kind of exceptions we have to catch here... } } } } if (!$input->getOption('summary')) { $this->sortMigrationIndex($index, $input->getOption('sort-by')); } if (!$input->getOption('summary') && !$input->getOption('todo')) { if (count($index) > 50000) { $output->writeln("WARNING: printing the status table might take a while as it contains many rows. Please wait..."); } $output->writeln("\n == All Migrations\n"); } $summary = array( self::STATUS_INVALID => array('Invalid', 0), Migration::STATUS_TODO => array('To do', 0), Migration::STATUS_STARTED => array('Started', 0), Migration::STATUS_DONE => array('Done', 0), Migration::STATUS_SUSPENDED => array('Suspended', 0), Migration::STATUS_FAILED => array('Failed', 0), Migration::STATUS_SKIPPED => array('Skipped', 0), Migration::STATUS_PARTIALLY_DONE => array('Partially done', 0), ); $data = array(); $i = 1; foreach ($index as $name => $value) { if (!isset($value['migration'])) { $migrationDefinition = $migrationsService->parseMigrationDefinition($value['definition']); $notes = $displayPath ? $migrationDefinition->path : ''; if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) { if (!$displayPath) { $notes = '' . $migrationDefinition->parsingError . ''; } $summary[self::STATUS_INVALID][1]++; } else { $summary[Migration::STATUS_TODO][1]++; } if ($input->getOption('todo')) { $data[] = $migrationDefinition->path; } else { $data[] = array( $i++, $name, 'not executed', '', $notes ); } } else { /** @var Migration $migration */ $migration = $value['migration']; if (!isset($summary[$migration->status])) { $summary[$migration->status] = array($migration->status, 0); } $summary[$migration->status][1]++; if ($input->getOption('summary')) { continue; } if ($input->getOption('todo')) { if ($migration->status == Migration::STATUS_TODO) { $data[] = $migration->path; } continue; } switch ($migration->status) { case Migration::STATUS_DONE: $status = 'executed'; break; case Migration::STATUS_STARTED: $status = 'execution started'; break; case Migration::STATUS_TODO: // bold to-migrate! $status = 'not executed'; break; case Migration::STATUS_SKIPPED: $status = 'skipped'; break; case Migration::STATUS_PARTIALLY_DONE: $status = 'partially executed'; break; case Migration::STATUS_SUSPENDED: $status = 'suspended'; break; case Migration::STATUS_FAILED: $status = 'failed'; break; } if ($displayPath) { $notes = $migration->path; } else { $notes = array(); if ($migration->executionError != '') { $notes[] = "{$migration->executionError}"; } if (!isset($value['definition'])) { $notes[] = 'The migration definition file can not be found any more'; } else { $migrationDefinition = $value['definition']; if (md5($migrationDefinition->rawDefinition) != $migration->md5) { $notes[] = 'The migration definition file has now a different checksum'; } if ($migrationDefinition->path != $migrationDefinition->path) { $notes[] = 'The migration definition file has now moved'; } } $notes = implode(' ', $notes); } $data[] = array( $i++, $migration->name, $status, ($migration->executionDate != null ? date("Y-m-d H:i:s", $migration->executionDate) : ''), $notes ); } } if ($input->getOption('todo')) { foreach ($data as $migrationData) { $output->writeln("$migrationData", OutputInterface::OUTPUT_RAW|OutputInterface::VERBOSITY_QUIET); } return 0; } if ($input->getOption('summary')) { $output->writeln("\n == Migrations Summary\n"); // do not print info about the not yet supported case unset($summary[Migration::STATUS_PARTIALLY_DONE]); $data = $summary; $headers = array('Status', 'Count'); } else { $headers = array('#', 'Migration', 'Status', 'Executed on', $displayPath ? 'Path' : 'Notes'); } $table = new Table($output); $table ->setHeaders($headers) ->setRows($data); $table->render(); return 0; } /** * @param array[] $index * @param string $sortBy * @throws \Exception */ protected function sortMigrationIndex(array &$index, $sortBy) { switch($sortBy) { case 'execution': uasort($index, function($m1, $m2) { if (isset($m1['migration']) && $m1['migration']->executionDate !== null) { if (isset($m2['migration']) && $m2['migration']->executionDate !== null) { return $m1['migration']->executionDate - $m2['migration']->executionDate; } else { // m2 not executed: send to bottom return -1; } } else { if (isset($m2['migration']) && $m2['migration']->executionDate !== null) { // m1 not executed: send to bottom return 1; } else { // both not executed: compare by name return strcmp( isset($m1['migration']) ? $m1['migration']->name : $m1['definition']->name, isset($m2['migration']) ? $m2['migration']->name : $m2['definition']->name ); } } }); break; case 'name': ksort($index); break; default: throw new \Exception("Unsupported sort order: '$sortBy'"); } } }