File: /var/www/payments-gateway/vendor/doctrine/migrations/src/Tools/Console/Command/MigrateCommand.php
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Tools\Console\Command;
use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria;
use Doctrine\Migrations\Exception\NoMigrationsToExecute;
use Doctrine\Migrations\Exception\UnknownMigrationVersion;
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
use Doctrine\Migrations\Tools\Console\ConsoleInputMigratorConfigurationFactory;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function count;
use function dirname;
use function getcwd;
use function in_array;
use function is_dir;
use function is_string;
use function is_writable;
use function sprintf;
use function str_starts_with;
/**
* The MigrateCommand class is responsible for executing a migration from the current version to another
* version up or down. It will calculate all the migration versions that need to be executed and execute them.
*/
#[AsCommand(name: 'migrations:migrate', description: 'Execute a migration to a specified version or the latest available version.')]
final class MigrateCommand extends DoctrineCommand
{
/** @var string|null */
protected static $defaultName = 'migrations:migrate';
protected function configure(): void
{
$this
->setAliases(['migrate'])
->setDescription(
'Execute a migration to a specified version or the latest available version.',
)
->addArgument(
'version',
InputArgument::OPTIONAL,
'The version FQCN or alias (first, prev, next, latest) to migrate to.',
'latest',
)
->addOption(
'write-sql',
null,
InputOption::VALUE_OPTIONAL,
'The path to output the migration SQL file. Defaults to current working directory.',
false,
)
->addOption(
'dry-run',
null,
InputOption::VALUE_NONE,
'Execute the migration as a dry run.',
)
->addOption(
'query-time',
null,
InputOption::VALUE_NONE,
'Time all the queries individually.',
)
->addOption(
'allow-no-migration',
null,
InputOption::VALUE_NONE,
'Do not throw an exception if no migration is available.',
)
->addOption(
'all-or-nothing',
null,
InputOption::VALUE_OPTIONAL,
'Wrap the entire migration in a transaction.',
ConsoleInputMigratorConfigurationFactory::ABSENT_CONFIG_VALUE,
)
->addOption(
'no-all-or-nothing',
null,
InputOption::VALUE_NONE,
'Disable wrapping the entire migration in a transaction.',
)
->setHelp(<<<'EOT'
The <info>%command.name%</info> command executes a migration to a specified version or the latest available version:
<info>%command.full_name%</info>
You can show more information about the process by increasing the verbosity level. To see the
executed queries, set the level to debug with <comment>-vv</comment>:
<info>%command.full_name% -vv</info>
You can optionally manually specify the version you wish to migrate to:
<info>%command.full_name% FQCN</info>
You can specify the version you wish to migrate to using an alias:
<info>%command.full_name% prev</info>
<info>These alias are defined: first, latest, prev, current and next</info>
You can specify the version you wish to migrate to using a number against the current version:
<info>%command.full_name% current+3</info>
You can also execute the migration as a <comment>--dry-run</comment>:
<info>%command.full_name% FQCN --dry-run</info>
You can output the prepared SQL statements to a file with <comment>--write-sql</comment>:
<info>%command.full_name% FQCN --write-sql</info>
Or you can also execute the migration without a warning message which you need to interact with <comment>--no-interaction</comment>:
<info>%command.full_name% --no-interaction</info>
You can also time all the different queries if you want to know which one is taking so long with <comment>--query-time</comment>:
<info>%command.full_name% --query-time</info>
You can skip throwing an exception if no migration is available with <comment>--allow-no-migration</comment>:
<info>%command.full_name% --allow-no-migration</info>
You can wrap the entire migration in a transaction with <comment>--all-or-nothing</comment>:
<info>%command.full_name% --all-or-nothing</info>
EOT);
parent::configure();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory();
$migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input);
$databaseName = (string) $this->getDependencyFactory()->getConnection()->getDatabase();
$question = sprintf(
'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?',
$databaseName === '' ? '<unnamed>' : $databaseName,
);
if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) {
$this->io->error('Migration cancelled!');
return 3;
}
$this->getDependencyFactory()->getMetadataStorage()->ensureInitialized();
$allowNoMigration = $input->getOption('allow-no-migration');
$versionAlias = $input->getArgument('version');
$path = $input->getOption('write-sql') ?? getcwd();
if (is_string($path) && ! $this->isPathWritable($path)) {
$this->io->error(sprintf('The path "%s" not writeable!', $path));
return 1;
}
$migrationRepository = $this->getDependencyFactory()->getMigrationRepository();
if (count($migrationRepository->getMigrations()) === 0) {
$message = sprintf(
'The version "%s" couldn\'t be reached, there are no registered migrations.',
$versionAlias,
);
if ($allowNoMigration) {
$this->io->warning($message);
return 0;
}
$this->io->error($message);
return 1;
}
try {
$version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias($versionAlias);
} catch (UnknownMigrationVersion) {
$this->io->error(sprintf(
'Unknown version: %s',
OutputFormatter::escape($versionAlias),
));
return 1;
} catch (NoMigrationsToExecute | NoMigrationsFoundWithCriteria) {
return $this->exitForAlias($versionAlias);
}
$planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator();
$statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator();
$executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations();
if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input) === false) {
return 3;
}
$plan = $planCalculator->getPlanUntilVersion($version);
if (count($plan) === 0) {
return $this->exitForAlias($versionAlias);
}
$this->getDependencyFactory()->getLogger()->notice(
'Migrating' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {direction} to {to}',
[
'direction' => $plan->getDirection(),
'to' => (string) $version,
],
);
$migrator = $this->getDependencyFactory()->getMigrator();
$sql = $migrator->migrate($plan, $migratorConfiguration);
if (is_string($path)) {
$writer = $this->getDependencyFactory()->getQueryWriter();
$writer->write($path, $plan->getDirection(), $sql);
}
$this->io->success(sprintf(
'Successfully migrated to version: %s',
$version,
));
$this->io->newLine();
return 0;
}
private function checkExecutedUnavailableMigrations(
ExecutedMigrationsList $executedUnavailableMigrations,
InputInterface $input,
): bool {
if (count($executedUnavailableMigrations) !== 0) {
$this->io->warning(sprintf(
'You have %s previously executed migrations in the database that are not registered migrations.',
count($executedUnavailableMigrations),
));
foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) {
$this->io->text(sprintf(
'<comment>>></comment> %s (<comment>%s</comment>)',
$executedUnavailableMigration->getExecutedAt()?->format('Y-m-d H:i:s'),
$executedUnavailableMigration->getVersion(),
));
}
$question = 'Are you sure you wish to continue?';
if (! $this->canExecute($question, $input)) {
$this->io->error('Migration cancelled!');
return false;
}
}
return true;
}
private function exitForAlias(string $versionAlias): int
{
$version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias('current');
// Allow meaningful message when latest version already reached.
if (in_array($versionAlias, ['current', 'latest', 'first'], true)) {
$message = sprintf(
'Already at the %s version ("%s")',
$versionAlias,
(string) $version,
);
$this->io->success($message);
} elseif (in_array($versionAlias, ['next', 'prev'], true) || str_starts_with($versionAlias, 'current')) {
$message = sprintf(
'The version "%s" couldn\'t be reached, you are at version "%s"',
$versionAlias,
(string) $version,
);
$this->io->error($message);
} else {
$message = sprintf(
'You are already at version "%s"',
(string) $version,
);
$this->io->success($message);
}
return 0;
}
private function isPathWritable(string $path): bool
{
return is_writable($path) || is_dir($path) || is_writable(dirname($path));
}
}