Final Class Yiisoft\Db\Migration\Command\MarkCommand
| Inheritance | Yiisoft\ |
|---|
Moves migration history to a version without executing up() or down().
Use this command only after verifying that the database schema already matches the target migration state.
For example,
./yii migrate:mark 'App\Migrations\M260101000002CreateIndex' # record pending migrations through this class
./yii migrate:mark M260101000003AddColumn # use an unqualified migration class name
./yii migrate:mark 260101000004 # select a migration by its timestamp
./yii migrate:mark --clear # remove all migration history entries
A pending target is recorded along with all earlier pending migrations. A recorded target removes newer history entries. Use the full class name when several migrations share a timestamp.
Public Methods
| Method | Description | Defined By |
|---|---|---|
| __construct() | Yiisoft\ |
Protected Methods
| Method | Description | Defined By |
|---|---|---|
| configure() | Yiisoft\ |
|
| createContext() | Yiisoft\ |
|
| createDefaultContext() | Yiisoft\ |
|
| execute() | Yiisoft\ |
|
| executeForDatabase() | Yiisoft\ |
|
| getAvailableDatabaseNames() | Yiisoft\ |
|
| isMultipleDatabases() | Yiisoft\ |
|
| resolveDatabaseNamesWithMissingOption() | Resolves the database names or returns a message indicating that an option is missing if multiple databases are configured. | Yiisoft\ |
Method Details
| public mixed __construct ( Yiisoft\ | ||
| $migrationService | Yiisoft\ |
|
| $migrator | Yiisoft\ |
|
| $additionalDatabases | ?\ |
|
public function __construct(
private readonly MigrationService $migrationService,
private readonly Migrator $migrator,
?DatabaseRegistry $additionalDatabases = null,
) {
parent::__construct($additionalDatabases);
}
| protected void configure ( ) |
protected function configure(): void
{
$this
->addArgument('version', InputArgument::OPTIONAL, 'Migration class name or timestamp.')
->addOption('clear', 'c', InputOption::VALUE_NONE, 'Remove all migration history entries.')
->addOption('force-yes', 'y', InputOption::VALUE_NONE, 'Force yes to all questions.');
}
| protected Yiisoft\ | ||
| $setup | Yiisoft\ |
|
protected function createContext(DatabaseSetup $setup): MigrationContext
{
return new MigrationContext($setup->migrator, $setup->migrationService);
}
| protected Yiisoft\ |
protected function createDefaultContext(): MigrationContext
{
return new MigrationContext($this->migrator, $this->migrationService);
}
| protected integer execute ( \ | ||
| $input | \ |
|
| $output | \ |
|
final protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$names = $this->resolveDatabaseNames($input);
if (is_string($names)) {
$io->error($names);
return Command::INVALID;
}
$multipleDatabases = $this->isMultipleDatabases();
$hasResults = false;
foreach ($names as $name) {
if (!$multipleDatabases || $name === DatabaseRegistry::DEFAULT_DATABASE) {
$context = $this->createDefaultContext();
} else {
/** @psalm-var DatabaseRegistry $this->additionalDatabases */
$context = $this->createContext($this->additionalDatabases->createSetup($name));
}
if ($multipleDatabases) {
$io->section('Database: ' . OutputFormatter::escape($name));
}
$status = $this->executeForDatabase($input, $output, $context);
if ($status === null) {
continue;
}
if ($status !== Command::SUCCESS) {
return $status;
}
$hasResults = true;
}
return $hasResults ? Command::SUCCESS : Command::FAILURE;
}
| protected integer executeForDatabase ( \ | ||
| $input | \ |
|
| $output | \ |
|
| $context | object | |
protected function executeForDatabase(
InputInterface $input,
OutputInterface $output,
object $context,
): int {
$io = new SymfonyStyle($input, $output);
$context->migrator->setIo($io);
$context->migrationService->setIo($io);
$context->migrationService->databaseConnection();
/** @var string|null $version */
$version = $input->getArgument('version');
$clear = (bool) $input->getOption('clear');
if ($clear === ($version !== null)) {
$io->error('Specify either a version or the "--clear" option.');
return Command::INVALID;
}
$history = array_keys($context->migrator->getHistory());
if ($version === null) {
if ($history === []) {
$io->success('The migration history is already empty. Nothing needs to be done.');
return Command::SUCCESS;
}
$this->writeMigrations($output, $history, 'to be removed from history');
$io->note('Only migration history will change. No migrations will be reverted.');
if ($input->getOption('force-yes') || $io->confirm('Remove all migration history entries?', false)) {
$context->migrator->updateHistory([], $history);
$io->success('The migration history is cleared. No actual migration was performed.');
}
return Command::SUCCESS;
}
$version = trim($version, '\\');
$label = OutputFormatter::escape($version);
$timestamp = preg_match('/^\d{12}$/D' , $version) === 1 ? $version : null;
if ($timestamp === null && preg_match('/^(?:\w+\\\\)*M\d{12}.*$/D' , $version) !== 1) {
$io->error('The version must be a migration class name or a migration timestamp.');
return Command::INVALID;
}
$add = [];
$remove = [];
$found = false;
$isRecordedClass = $timestamp === null && in_array(
$version,
array_map(static fn(string $name): string => trim($name, '\\'), $history),
true,
);
// Exact recorded classes do not need source files. Timestamps still select pending targets first.
if (!$isRecordedClass) {
$pending = $context->migrationService->getNewMigrations();
foreach ($pending as $i => $migration) {
if ($this->matches($migration, $version, $timestamp)) {
$add = array_slice($pending, 0, $i + 1);
$found = true;
break;
}
}
}
if (!$found) {
foreach ($history as $i => $migration) {
if ($this->matches($migration, $version, $timestamp)) {
$remove = array_slice($history, 0, $i);
$found = true;
break;
}
}
}
if (!$found) {
$io->error("Unable to find the version '$label'.");
return Command::INVALID;
}
if ($add === [] && $remove === []) {
$io->success("Already at '$label'. Nothing needs to be done.");
return Command::SUCCESS;
}
$limit = $context->migrator->getMigrationNameLimit();
foreach ($add as $migration) {
if ($limit !== null && strlen($migration) > $limit) {
$io->error('The migration name "' . OutputFormatter::escape($migration) . '" is too long.');
return Command::INVALID;
}
}
$this->writeMigrations($output, $add, 'to be marked as applied');
$this->writeMigrations($output, $remove, 'to be removed from history');
$io->note('Only migration history will change. No migrations will be applied or reverted.');
if ($input->getOption('force-yes') || $io->confirm("Set migration history at $label?", false)) {
$context->migrator->updateHistory($add, $remove);
$io->success("The migration history is set at $label. No actual migration was performed.");
}
return Command::SUCCESS;
}
| protected string[] getAvailableDatabaseNames ( ) |
protected function getAvailableDatabaseNames(): array
{
return [DatabaseRegistry::DEFAULT_DATABASE, ...($this->additionalDatabases?->getNames() ?? [])];
}
| protected boolean isMultipleDatabases ( ) |
protected function isMultipleDatabases(): bool
{
return $this->additionalDatabases !== null && $this->additionalDatabases->getNames() !== [];
}
Defined in:
Yiisoft\
Resolves the database names or returns a message indicating that an option is missing if multiple databases are configured.
| protected string[]|string resolveDatabaseNamesWithMissingOption ( \ | ||
| $input | \ |
The input interface containing the command options and arguments. |
protected function resolveDatabaseNamesWithMissingOption(InputInterface $input): array|string
{
if (!$this->isMultipleDatabases()) {
return [DatabaseRegistry::DEFAULT_DATABASE];
}
return 'The --db option is required when multiple databases are configured.';
}
User Contributed Notes
Leave a comment
Join the conversation to share a note.