Skip to content

Commit

Permalink
Merge pull request #917 from goetas/up-to-date-command-extended
Browse files Browse the repository at this point in the history
Extended details on up to date migrations
  • Loading branch information
goetas authored Jan 16, 2020
2 parents d8679a8 + 849712c commit 93927c3
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 87 deletions.
4 changes: 3 additions & 1 deletion lib/Doctrine/Migrations/DependencyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,9 @@ public function getMigrationStatusInfosHelper() : MigrationStatusInfosHelper
return new MigrationStatusInfosHelper(
$this->getConfiguration(),
$this->getConnection(),
$this->getVersionAliasResolver()
$this->getVersionAliasResolver(),
$this->getMigrationRepository(),
$this->getMetadataStorage()
);
});
}
Expand Down
60 changes: 4 additions & 56 deletions lib/Doctrine/Migrations/Tools/Console/Command/ListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@

namespace Doctrine\Migrations\Tools\Console\Command;

use DateTimeInterface;
use Doctrine\Migrations\Metadata\AvailableMigration;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigration;
use Doctrine\Migrations\Metadata\ExecutedMigrationsSet;
use Doctrine\Migrations\Version\Version;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function array_map;
Expand Down Expand Up @@ -44,63 +41,14 @@ protected function configure() : void

public function execute(InputInterface $input, OutputInterface $output) : int
{
$this->showVersions(
$versions = $this->getSortedVersions(
$this->getDependencyFactory()->getMigrationRepository()->getMigrations(), // available migrations
$this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations(), // executed migrations
$output
$this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations() // executed migrations
);

return 0;
}

private function showVersions(
AvailableMigrationsList $availableMigrations,
ExecutedMigrationsSet $executedMigrations,
OutputInterface $output
) : void {
$table = new Table($output);
$table->setHeaders(
[
[new TableCell('Migration Versions', ['colspan' => 4])],
['Migration', 'Status', 'Migrated At', 'Execution Time', 'Description'],
]
);

foreach ($this->getSortedVersions($availableMigrations, $executedMigrations) as $version) {
$description = null;
$executedAt = null;
$executionTime = null;
$this->getDependencyFactory()->getMigrationStatusInfosHelper()->listVersions($versions, $output);

if ($executedMigrations->hasMigration($version)) {
$executedMigration = $executedMigrations->getMigration($version);
$executionTime = $executedMigration->getExecutionTime();
$executedAt = $executedMigration->getExecutedAt() instanceof DateTimeInterface
? $executedMigration->getExecutedAt()->format('Y-m-d H:i:s')
: null;
}

if ($availableMigrations->hasMigration($version)) {
$description = $availableMigrations->getMigration($version)->getMigration()->getDescription();
}

if ($executedMigrations->hasMigration($version) && $availableMigrations->hasMigration($version)) {
$status = '<info>migrated</info>';
} elseif ($executedMigrations->hasMigration($version)) {
$status = '<error>migrated, not available</error>';
} else {
$status = '<comment>not migrated</comment>';
}

$table->addRow([
(string) $version,
$status,
(string) $executedAt,
$executionTime !== null ? $executionTime . 's': '',
$description,
]);
}

$table->render();
return 0;
}

/**
Expand Down
68 changes: 48 additions & 20 deletions lib/Doctrine/Migrations/Tools/Console/Command/UpToDateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@

namespace Doctrine\Migrations\Tools\Console\Command;

use Doctrine\Migrations\Exception\MetadataStorageError;
use Doctrine\Migrations\Metadata\AvailableMigration;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigration;
use Doctrine\Migrations\Metadata\ExecutedMigrationsSet;
use Doctrine\Migrations\Version\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function array_map;
use function array_merge;
use function array_unique;
use function count;
use function sprintf;
use function uasort;

/**
* The UpToDateCommand class outputs if your database is up to date or if there are new migrations
Expand All @@ -26,6 +34,7 @@ protected function configure() : void
->setAliases(['up-to-date'])
->setDescription('Tells you if your schema is up-to-date.')
->addOption('fail-on-unregistered', 'u', InputOption::VALUE_NONE, 'Whether to fail when there are unregistered extra migrations found')
->addOption('list-migrations', 'l', InputOption::VALUE_NONE, 'Show a list of missing or not migrated versions.')
->setHelp(<<<EOT
The <info>%command.name%</info> command tells you if your schema is up-to-date:
Expand All @@ -40,48 +49,67 @@ public function execute(InputInterface $input, OutputInterface $output) : ?int
{
$statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator();

try {
$executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations();
} catch (MetadataStorageError $metadataStorageError) {
$output->writeln(sprintf(
'<error>%s</error>',
$metadataStorageError->getMessage()
));

return 3;
}

$newMigrations = $statusCalculator->getNewMigrations();

$executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations();
$newMigrations = $statusCalculator->getNewMigrations();
$newMigrationsCount = count($newMigrations);
$executedUnavailableMigrationsCount = count($executedUnavailableMigrations);
$executedUnavailableMigrationsCount = count($executedUnavailableMigrations);

if ($newMigrationsCount === 0 && $executedUnavailableMigrationsCount ===0) {
if ($newMigrationsCount === 0 && $executedUnavailableMigrationsCount === 0) {
$output->writeln('<comment>Up-to-date! No migrations to execute.</comment>');

return 0;
}

$exitCode = 0;
if ($newMigrationsCount > 0) {
$output->writeln(sprintf(
'<error>Out-of-date! %u migration%s available to execute.</error>',
$newMigrationsCount,
$newMigrationsCount > 1 ? 's are' : ' is'
));

return 1;
$exitCode = 1;
}

// negative number means that there are unregistered migrations in the database
if ($executedUnavailableMigrationsCount > 0) {
$output->writeln(sprintf(
'<error>You have %1$u previously executed migration%3$s in the database that %2$s registered migration%3$s.</error>',
$executedUnavailableMigrationsCount,
$executedUnavailableMigrationsCount > 1 ? 'are not' : 'is not a',
$executedUnavailableMigrationsCount > 1 ? 's' : ''
));
if ($input->getOption('fail-on-unregistered')) {
$exitCode = 2;
}
}

if ($input->getOption('list-migrations')) {
$versions = $this->getSortedVersions($newMigrations, $executedUnavailableMigrations);
$this->getDependencyFactory()->getMigrationStatusInfosHelper()->listVersions($versions, $output);
}

return $executedUnavailableMigrationsCount > 0 && $input->getOption('fail-on-unregistered') === true ? 2 : 0;
return $exitCode;
}

/**
* @return Version[]
*/
private function getSortedVersions(AvailableMigrationsList $newMigrations, ExecutedMigrationsSet $executedUnavailableMigrations) : array
{
$executedUnavailableVersion = array_map(static function (ExecutedMigration $executedMigration) : Version {
return $executedMigration->getVersion();
}, $executedUnavailableMigrations->getItems());

$newVersions = array_map(static function (AvailableMigration $availableMigration) : Version {
return $availableMigration->getVersion();
}, $newMigrations->getItems());

$versions = array_unique(array_merge($executedUnavailableVersion, $newVersions));

$comparator = $this->getDependencyFactory()->getVersionComparator();
uasort($versions, static function (Version $a, Version $b) use ($comparator) : int {
return $comparator->compare($a, $b);
});

return $versions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

namespace Doctrine\Migrations\Tools\Console\Helper;

use DateTimeInterface;
use Doctrine\DBAL\Connection;
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigrationsSet;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\Migrations\MigrationRepository;
use Doctrine\Migrations\Version\AliasResolver;
use Doctrine\Migrations\Version\Version;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
Expand Down Expand Up @@ -39,14 +43,76 @@ class MigrationStatusInfosHelper
/** @var AliasResolver */
private $aliasResolver;

/** @var MetadataStorage */
private $metadataStorage;

/** @var MigrationRepository */
private $migrationRepository;

public function __construct(
Configuration $configuration,
Connection $connection,
AliasResolver $aliasResolver
AliasResolver $aliasResolver,
MigrationRepository $migrationRepository,
MetadataStorage $metadataStorage
) {
$this->configuration = $configuration;
$this->connection = $connection;
$this->aliasResolver = $aliasResolver;
$this->configuration = $configuration;
$this->connection = $connection;
$this->aliasResolver = $aliasResolver;
$this->migrationRepository = $migrationRepository;
$this->metadataStorage = $metadataStorage;
}

/**
* @param Version[] $versions
*/
public function listVersions(array $versions, OutputInterface $output) : void
{
$table = new Table($output);
$table->setHeaders(
[
[new TableCell('Migration Versions', ['colspan' => 4])],
['Migration', 'Status', 'Migrated At', 'Execution Time', 'Description'],
]
);
$executedMigrations = $this->metadataStorage->getExecutedMigrations();
$availableMigrations = $this->migrationRepository->getMigrations();

foreach ($versions as $version) {
$description = null;
$executedAt = null;
$executionTime = null;

if ($executedMigrations->hasMigration($version)) {
$executedMigration = $executedMigrations->getMigration($version);
$executionTime = $executedMigration->getExecutionTime();
$executedAt = $executedMigration->getExecutedAt() instanceof DateTimeInterface
? $executedMigration->getExecutedAt()->format('Y-m-d H:i:s')
: null;
}

if ($availableMigrations->hasMigration($version)) {
$description = $availableMigrations->getMigration($version)->getMigration()->getDescription();
}

if ($executedMigrations->hasMigration($version) && $availableMigrations->hasMigration($version)) {
$status = '<info>migrated</info>';
} elseif ($executedMigrations->hasMigration($version)) {
$status = '<error>migrated, not available</error>';
} else {
$status = '<comment>not migrated</comment>';
}

$table->addRow([
(string) $version,
$status,
(string) $executedAt,
$executionTime !== null ? $executionTime . 's': '',
$description,
]);
}

$table->render();
}

public function showMigrationsInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\Migrations\Tests\Tools\Console\Command;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\Configuration\Configuration;
Expand All @@ -21,7 +22,10 @@
use Doctrine\Migrations\Version\ExecutionResult;
use Doctrine\Migrations\Version\Version;
use Symfony\Component\Console\Tester\CommandTester;
use function array_map;
use function explode;
use function sys_get_temp_dir;
use function trim;

class UpToDateCommandTest extends MigrationTestCase
{
Expand Down Expand Up @@ -91,17 +95,45 @@ public function testIsUpToDate(array $migrations, array $migratedVersions, int $
self::assertSame($exitCode, $this->commandTester->getStatusCode());
}

public function testNoMetadataStorage() : void
public function testMigrationList() : void
{
$this->conn->getSchemaManager()->dropTable($this->metadataConfig->getTableName());
$migrationClass = $this->createMock(AbstractMigration::class);
$migrationClass
->expects(self::atLeastOnce())
->method('getDescription')
->willReturn('foo');

Helper::registerMigrationInstance($this->migrationRepository, new Version('1231'), $migrationClass);
Helper::registerMigrationInstance($this->migrationRepository, new Version('1230'), $migrationClass);

$result = new ExecutionResult(new Version('1230'), Direction::UP, new DateTimeImmutable('2010-01-01 02:03:04'));
$result->setTime(10.0);
$this->metadataStorage->complete($result);

$result = new ExecutionResult(new Version('1229'), Direction::UP, new DateTimeImmutable('2010-01-01 02:03:04'));
$this->metadataStorage->complete($result);

$this->commandTester->execute([]);

self::assertStringContainsString(
'The metadata storage is not initialized, please run the sync-metadata-storage command to fix this issue.',
$this->commandTester->getDisplay()
$this->commandTester->execute(['--list-migrations' => true]);

$lines = array_map('trim', explode("\n", trim($this->commandTester->getDisplay(true))));

self::assertSame(
[
'Out-of-date! 1 migration is available to execute.',
'You have 1 previously executed migration in the database that is not a registered migration.',
'+-----------+-------------------------+---------------------+----------------+-------------+',
'| Migration Versions | |',
'+-----------+-------------------------+---------------------+----------------+-------------+',
'| Migration | Status | Migrated At | Execution Time | Description |',
'+-----------+-------------------------+---------------------+----------------+-------------+',
'| 1229 | migrated, not available | 2010-01-01 02:03:04 | | |',
'| 1231 | not migrated | | | foo |',
'+-----------+-------------------------+---------------------+----------------+-------------+',
],
$lines
);
self::assertSame(3, $this->commandTester->getStatusCode());
}

/**
Expand Down

0 comments on commit 93927c3

Please sign in to comment.