Skip to content

Commit

Permalink
add commands to migrate table structure
Browse files Browse the repository at this point in the history
  • Loading branch information
goetas committed Dec 1, 2019
1 parent f92df45 commit 666b3eb
Show file tree
Hide file tree
Showing 17 changed files with 448 additions and 46 deletions.
20 changes: 20 additions & 0 deletions lib/Doctrine/Migrations/Exception/MetadataStorageError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Doctrine\Migrations\Exception;

use RuntimeException;

final class MetadataStorageError extends RuntimeException implements MigrationException
{
public static function notUpToDate() : self
{
return new self('The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue.');
}

public static function notInitialized() : self
{
return new self('The metadata storage is not initialized, please run the sync-metadata-storage command to fix this issue.');
}
}
2 changes: 2 additions & 0 deletions lib/Doctrine/Migrations/Metadata/Storage/MetadataStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

interface MetadataStorage
{
public function ensureInitialized() : void;

public function getExecutedMigrations() : ExecutedMigrationsSet;

public function complete(ExecutionResult $migration) : void;
Expand Down
164 changes: 130 additions & 34 deletions lib/Doctrine/Migrations/Metadata/Storage/TableMetadataStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
use Doctrine\DBAL\Connections\MasterSlaveConnection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Types\Type;
use Doctrine\Migrations\Exception\MetadataStorageError;
use Doctrine\Migrations\Metadata\AvailableMigration;
use Doctrine\Migrations\Metadata\ExecutedMigration;
use Doctrine\Migrations\Metadata\ExecutedMigrationsSet;
use Doctrine\Migrations\MigrationRepository;
use Doctrine\Migrations\Version\Direction;
use Doctrine\Migrations\Version\ExecutionResult;
use Doctrine\Migrations\Version\Version;
Expand All @@ -21,6 +26,8 @@
use function array_change_key_case;
use function intval;
use function sprintf;
use function strlen;
use function strpos;
use function strtolower;

final class TableMetadataStorage implements MetadataStorage
Expand All @@ -37,46 +44,28 @@ final class TableMetadataStorage implements MetadataStorage
/** @var TableMetadataStorageConfiguration */
private $configuration;

public function __construct(Connection $connection, ?MetadataStorageConfiguration $configuration = null)
{
$this->connection = $connection;
$this->schemaManager = $connection->getSchemaManager();
$this->platform = $connection->getDatabasePlatform();
/** @var MigrationRepository|null */
private $migrationRepository;

public function __construct(
Connection $connection,
?MetadataStorageConfiguration $configuration = null,
?MigrationRepository $migrationRepository = null
) {
$this->migrationRepository = $migrationRepository;
$this->connection = $connection;
$this->schemaManager = $connection->getSchemaManager();
$this->platform = $connection->getDatabasePlatform();

if ($configuration !== null && ! ($configuration instanceof TableMetadataStorageConfiguration)) {
throw new InvalidArgumentException(sprintf('%s accepts only %s as configuration', self::class, TableMetadataStorageConfiguration::class));
}
$this->configuration = $configuration ?: new TableMetadataStorageConfiguration();
}

private function isInitialized() : bool
{
if ($this->connection instanceof MasterSlaveConnection) {
$this->connection->connect('master');
}

return $this->schemaManager->tablesExist([$this->configuration->getTableName()]);
}

private function initialize() : void
{
$schemaChangelog = new Table($this->configuration->getTableName());

$schemaChangelog->addColumn($this->configuration->getVersionColumnName(), 'string', ['notnull' => true, 'length' => $this->configuration->getVersionColumnLength()]);
$schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]);
$schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]);

$schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]);

$this->schemaManager->createTable($schemaChangelog);
}

public function getExecutedMigrations() : ExecutedMigrationsSet
{
if (! $this->isInitialized()) {
$this->initialize();
}

$this->checkInitialization();
$rows = $this->connection->fetchAll(sprintf('SELECT * FROM %s', $this->configuration->getTableName()));

$migrations = [];
Expand Down Expand Up @@ -108,6 +97,8 @@ public function getExecutedMigrations() : ExecutedMigrationsSet

public function reset() : void
{
$this->checkInitialization();

$this->connection->executeUpdate(
sprintf(
'DELETE FROM %s WHERE 1 = 1',
Expand All @@ -118,9 +109,7 @@ public function reset() : void

public function complete(ExecutionResult $result) : void
{
if (! $this->isInitialized()) {
$this->initialize();
}
$this->checkInitialization();

if ($result->getDirection() === Direction::DOWN) {
$this->connection->delete($this->configuration->getTableName(), [
Expand All @@ -138,4 +127,111 @@ public function complete(ExecutionResult $result) : void
]);
}
}

public function ensureInitialized() : void
{
$expectedSchemaChangelog = $this->getExpectedTable();

if (! $this->isInitialized($expectedSchemaChangelog)) {
$this->schemaManager->createTable($expectedSchemaChangelog);

return;
}

$diff = $this->needsUpdate($expectedSchemaChangelog);
if ($diff === null) {
return;
}

$this->schemaManager->alterTable($diff);
$this->updateMigratedVersionsFromV1orV2toV3();
}

private function needsUpdate(Table $expectedTable) : ?TableDiff
{
$comparator = new Comparator();
$currentTable = $this->schemaManager->listTableDetails($this->configuration->getTableName());
$diff = $comparator->diffTable($currentTable, $expectedTable);

return $diff instanceof TableDiff ? $diff : null;
}

private function isInitialized(Table $expectedTable) : bool
{
if ($this->connection instanceof MasterSlaveConnection) {
$this->connection->connect('master');
}

return $this->schemaManager->tablesExist([$expectedTable->getName()]);
}

private function checkInitialization() : void
{
$expectedTable = $this->getExpectedTable();

if (! $this->isInitialized($expectedTable)) {
throw MetadataStorageError::notInitialized();
}

if ($this->needsUpdate($expectedTable)!== null) {
throw MetadataStorageError::notUpToDate();
}
}

private function getExpectedTable() : Table
{
$schemaChangelog = new Table($this->configuration->getTableName());

$schemaChangelog->addColumn(
$this->configuration->getVersionColumnName(),
'string',
['notnull' => true, 'length' => $this->configuration->getVersionColumnLength()]
);
$schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]);
$schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]);

$schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]);

return $schemaChangelog;
}

private function updateMigratedVersionsFromV1orV2toV3() : void
{
if ($this->migrationRepository === null) {
return;
}

$availableMigrations = $this->migrationRepository->getMigrations()->getItems();
$executedMigrations = $this->getExecutedMigrations()->getItems();

foreach ($availableMigrations as $availableMigration) {
foreach ($executedMigrations as $k => $executedMigration) {
if ($this->isAlreadyV3Format($availableMigration, $executedMigration)) {
continue;
}

$this->connection->update(
$this->configuration->getTableName(),
[
$this->configuration->getVersionColumnName() => (string) $availableMigration->getVersion(),
],
[
$this->configuration->getVersionColumnName() => (string) $executedMigration->getVersion(),
]
);
unset($executedMigrations[$k]);
}
}
}

private function isAlreadyV3Format(AvailableMigration $availableMigration, ExecutedMigration $executedMigration) : bool
{
return strpos(
(string) $availableMigration->getVersion(),
(string) $executedMigration->getVersion()
) !== (
strlen((string) $availableMigration->getVersion()) -
strlen((string) $executedMigration->getVersion())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public function execute(InputInterface $input, OutputInterface $output) : ?int
return 1;
}

$this->getDependencyFactory()->getMetadataStorage()->ensureInitialized();
$migrator->migrate($plan, $migratorConfiguration);

return 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ public function execute(InputInterface $input, OutputInterface $output) : ?int
return 3;
}

$this->getDependencyFactory()->getMetadataStorage()->ensureInitialized();
$migrator->migrate($plan, $migratorConfiguration);

return 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Doctrine\Migrations\Tools\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class SyncMetadataCommand extends DoctrineCommand
{
/** @var string */
protected static $defaultName = 'migrations:sync-metadata-storage';

protected function configure() : void
{
parent::configure();

$this
->setAliases(['sync-metadata-storage'])
->setDescription('Ensures that the metadata storage is at the latest version.')
->setHelp(<<<EOT
The <info>%command.name%</info> command updates metadata storage the latest version.
<info>%command.full_name%</info>
EOT
);
}

public function execute(
InputInterface $input,
OutputInterface $output
) : int {
$this->getDependencyFactory()->getMetadataStorage()->ensureInitialized();

$output->writeln('Metadata storage synchronized');

return 0;
}
}
17 changes: 14 additions & 3 deletions lib/Doctrine/Migrations/Tools/Console/Command/UpToDateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\Migrations\Tools\Console\Command;

use Doctrine\Migrations\Exception\MetadataStorageError;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
Expand Down Expand Up @@ -37,9 +38,19 @@ protected function configure() : void

public function execute(InputInterface $input, OutputInterface $output) : ?int
{
$planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator();
$executedUnavailableMigrations = $planCalculator->getExecutedUnavailableMigrations();
$newMigrations = $planCalculator->getNewMigrations();
$planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator();
try {
$executedUnavailableMigrations = $planCalculator->getExecutedUnavailableMigrations();
} catch (MetadataStorageError $metadataStorageError) {
$output->writeln(sprintf(
'<error>%s</error>',
$metadataStorageError->getMessage()
));

return 3;
}

$newMigrations = $planCalculator->getNewMigrations();

$newMigrationsCount = count($newMigrations);
$executedUnavailableMigrationsCount = count($executedUnavailableMigrations);
Expand Down
2 changes: 2 additions & 0 deletions lib/Doctrine/Migrations/Tools/Console/ConsoleRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\Migrations\Tools\Console\Command\RollupCommand;
use Doctrine\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand;
use Doctrine\Migrations\Tools\Console\Command\UpToDateCommand;
use Doctrine\Migrations\Tools\Console\Command\VersionCommand;
use PackageVersions\Versions;
Expand Down Expand Up @@ -59,6 +60,7 @@ public static function addCommands(Application $cli) : void
new StatusCommand(),
new VersionCommand(),
new UpToDateCommand(),
new SyncMetadataCommand(),
]);

if (! $cli->getHelperSet()->has('em')) {
Expand Down
Loading

0 comments on commit 666b3eb

Please sign in to comment.