运行 同一命令中的多个迁移

Running multiple migrations in the same command

我有一个多租户 Symfony 5 应用程序,它包含一个负责全局数据的通用数据库和一个动态数量的租户数据库,它们之间使用相同的模式,负责租户特定的数据存储。

我总共有 2 个实体管理器,连接到通用数据库的 default 和使用可以在任何租户之间切换的包装器 (MultiDbConnectionWrapper) 的 tenant数据库。

config/doctrine.yaml

doctrine:
    dbal:
        connections:
            default:
                url: '%env(resolve:DATABASE_URL_CORE)%'
            tenant:
                url: '%env(resolve:DATABASE_URL_TENANT)%'
                wrapper_class: App\Dbal\MultiDbConnectionWrapper
    orm:
        auto_generate_proxy_classes: true
        entity_managers:
            default:
                connection: default
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                mappings:
                    Core:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Core'
                        prefix: 'App\Entity\Core'
                        alias: Core
            tenant:
                connection: default
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                mappings:
                    Tenant:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Tenant'
                        prefix: 'App\Entity\Tenant'
                        alias: tenant

对于每个 EM,我使用不同的迁移目录以及不同的迁移配置文件/

config/migrations/tenant_migrations.yaml

migrations_paths:
    'TenantMigrations': 'migrations/tenant'
em: 'tenant'

config/migrations/core_migrations.yaml

migrations_paths:
  'CoreMigrations': 'migrations/core'
em: 'default'

我的问题是我正在尝试创建一个命令,该命令将循环遍历所有租户数据库并运行 对每个数据库进行迁移。在我的主数据库中,我有一个名为 tenants 的 table,我在其中存储了该租户数据库的名称。

为此,我创建了以下命令:

src/Command/MigrateTenantsCommand.php

protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $tenants = $this->tenantRepository->findAll();
        $connection = $this->tenantEntityManager->getConnection();

        foreach ($tenants as $tenant){
            $db = $tenant->getDatabaseUuid();
            $connection->selectDatabase($db);

            $this->getApplication()
                ->find('doctrine:migrations:migrate')
                ->run(new ArrayInput([
                    '--configuration' => 'config/migrations/tenant_migrations.yaml',
                    '--no-interaction' => true
                ]),$output);
        }


        return Command::SUCCESS;
    }

问题是,如果我在数据库中存储了至少 2 个租户,并且命令 doctrine:migrations:migrate 必须 运行 两次,在第二个 运行 时它会抛出以下错误:

In FrozenDependencies.php line 13:
                                                             
  The dependencies are frozen and cannot be edited anymore.  
                                                            

从我的尝试来看,这似乎与我在 $connection->selectDatabase($db); 处更改数据库这一事实无关,但我不能 运行 两次 [=] 21=] 在命令中。

我也尝试了 运行 宁以下命令,但我收到了同样的错误:

protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->getApplication()
            ->find('doctrine:migrations:migrate')
            ->run(new ArrayInput([
                '--configuration' => 'config/migrations/core_migrations.yaml'
            ]),$output);
        $this->getApplication()
            ->find('doctrine:migrations:migrate')
            ->run(new ArrayInput([
                '--configuration' => 'config/migrations/core_migrations.yaml'
            ]),$output);
        return Command::SUCCESS;
    }

有人知道为什么会这样吗?谢谢!

我是如何解决这个问题的:

似乎 doctrine:migrations:migrate 锁定了自己,以防止来自同一命令的多个 运行。

为了绕过这个,我们可以运行在每个循环的不同进程中执行命令。

protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $companies = $this->companyRepository->findAll();
        foreach ($companies as $company){
            $db = $company->getDatabaseUuid();
            $connection = $this->tenantEntityManager->getConnection();
            $connection->selectDatabase($db);
            $process = new Process([
                "bin/console",
                "doctrine:migrations:migrate",
                "--configuration=config/migrations/tenant_migrations.yaml",
                "--em=tenant",
            ]);
            $process->run();
            if (!$process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }
            echo $process->getOutput();
        }
        return Command::SUCCESS;
    }

这会导致第二个问题。 由于我们在当前命令进程中 运行 $connection->selectDatabase($db);,但在新进程中 doctrine:migrations:migrate,因此实体管理器不会选择数据库。

解决方案是创建如下 2 个命令:

  • migrate:single-tenant 命令可以接收 --db 参数和 运行s doctrine:migrations:migrate 默认方式

  • migrate:tenants 命令获取所有数据库和 运行 migrate:single-tenant --db=$db 作为循环中的新进程

通过这种方式,我们将数据库设置在与执行 doctrine:migrations:migrate 相同的进程上,同时每个进程只保留一次迁移

这是最后的命令

迁移:单租户

protected function configure(): void
    {
        $this
            ->addOption('db', null, InputOption::VALUE_REQUIRED, 'Database selection')
        ;
    }
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $db = $input->getOption('db');
        if (!$db) {
            $io->error('"--db" option missing');
            return Command::FAILURE;
        }
        $connection = $this->tenantEntityManager->getConnection();
        $connection->selectDatabase($db);
        $this->getApplication()
            ->find('doctrine:migrations:migrate')
            ->run(new ArrayInput([
                '--configuration' => 'config/migrations/tenant_migrations.yaml',
                '--em' => 'tenant'
            ]),$output);
        $io->success("$db migrated succesfully");
        return Command::SUCCESS;
    }

迁移:租户

protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $companies = $this->companyRepository->findAll();
        foreach ($companies as $company){
            $db = $company->getDatabaseUuid();
            $process = new Process([
                "bin/console",
                "migrate:single-tenant",
                "--db=$db",
            ]);
            $process->run();
            if (!$process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }
            echo $process->getOutput();
        }
        return Command::SUCCESS;
    }