symfonydoctrinemigration

Doctrine dynamic Schema migration (on specific database)


I'm currently facing an issue in migration with doctrine and my multiple database (db_name)


I have inside the same MYSQL instance multiple database, one for the company and the other for each Client (Entity).

Each database is created inside the postPersit operation (i'm running a custom command that override the database:create of doctrine to create a database with the uuid of the client, this is working).

I want to do the same with the migration but I'm facing an error "config not found" and when I put the general configuration, it do not found what it needs.

I created a WrapperConnection and added the possibility to select the database. I'm using this wrapper before running an API platform request (inside an ApiNormalizer).

My code ...

#[AsCommand(name: 'custom:migration:migrate')]
final class MigrationCommand extends DoctrineCommand
{
    use EntityManagerTrait;
    use ParameterBagTrait;

    protected function configure(): void
    {
        $this
            ->setAliases(['migrate'])
            ->setDescription(
                'Execute a migration to a specified version or the latest available version.',
            )
            ->addArgument(
                'name',
                InputArgument::REQUIRED,
                'The database name'
            )
        ;

        parent::configure();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $databaseName = $input->getArgument('name');

        if (null === $databaseName) {
            // return Command::FAILURE;
        }

        /** @var MultiDbConnectionWrapper */
        $connection = $this->em->getConnection();
        $connection->selectDatabase($databaseName);
        
        // Doctrine code is below.

        $migrateCmd = new DoctrineMigrateCommand($this->getDependencyFactory());

        $migrateInput = new ArrayInput([
            '--configuration' => $this->parameterBag->get('kernel.project_dir').'/config/packages/doctrine.yaml',
        ]);

        $migrateCmd->run($migrateInput, $output);
     }
}

I tried to copy/past directly the code of the class to edit it... I failed ...

I want to use the migration on dynamic schema but I'm new on doctrine code and migration. Can u help me ?


If I knew how the config is working, I could have created copies of the migration files with the good schema I think and run it ... what do you think ?

Sorry for my approximative english.


Solution

  • I found it.

    Here is the solution

    <?php
    
    namespace App\Command\Doctrine;
    
    use App\Service\Trait\ParameterBagTrait;
    use Doctrine\Migrations\Tools\Console\Command\MigrateCommand as DoctrineMigrateCommand;
    use Symfony\Component\Console\Attribute\AsCommand;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\ArrayInput;
    use Symfony\Component\Console\Input\InputArgument;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    use Symfony\Component\Filesystem\Filesystem;
    use Symfony\Component\Yaml\Yaml;
    
    // the name of the command is what users type after "php bin/console"
    #[AsCommand(name: 'alx:migration:migrate', aliases: ['alx:mi:mi', 'alx-migrate'])]
    final class MigrationCommand extends Command
    {
        use ParameterBagTrait;
    
        protected function configure(): void
        {
            $this
                ->setAliases(['migrate'])
                ->setDescription(
                    'Execute a migration on a specific database',
                )
                ->addArgument(
                    'name',
                    InputArgument::REQUIRED,
                    'The database name'
                )
            ;
    
            parent::configure();
        }
    
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $databaseName = $input->getArgument('name');
    
            if (null === $databaseName) {
                return Command::FAILURE;
            }
    
            // Create the tmp file.
            $dbPath = $this->getDatabaseConfigPath($databaseName);
            $migrationPath = $this->getMigrationConfigPath();
    
            $migrateCmd = new DoctrineMigrateCommand();
            $migrateInput = new ArrayInput([
                '--db-configuration' => $dbPath,
                '--configuration' => $migrationPath,
            ]);
    
            // I dont want it to ask the question.
            $migrateInput->setInteractive(false);
            // execute the migration.
            $migrateCmd->run($migrateInput, $output);
    
            // Delete the tmp file.
            unlink($dbPath);
            unlink($migrationPath);
    
            return 0;
        }
    
        public function getDatabaseConfigPath(string $dbName): string
        {
            $urlDb = $this->parameterBag->get('database_url');
            $url = str_replace('bastion', $dbName, $urlDb);
    
            $phpContent = "<?php return ['url' => '$url']; ?>";
    
            return $this->createTmpFile('db_'.$dbName.'_'.uniqid().'.php', $phpContent);
        }
    
        public function getMigrationConfigPath(): string
        {
            /** @var string $directory */
            $directory = $this->parameterBag->get('kernel.project_dir');
            $migrationPath = $directory.'/migrations';
            $content = Yaml::dump([
                'migrations_paths' => [
                    'DoctrineMigrations' => $migrationPath,
                ],
                'table_storage' => [
                    'table_name' => 'doctrine_migration_versions',
                    'version_column_name' => 'version',
                    'version_column_length' => '191',
                ],
            ]);
    
            $name = uniqid('migration_');
    
            return $this->createTmpFile($name.'.yaml', $content);
        }
    
        private function createTmpFile(string $filename, string $content): string
        {
            $filesystem = new Filesystem();
            $tempFilePath = sys_get_temp_dir().'/'.$filename;
            $filesystem->dumpFile($tempFilePath, $content);
    
            return $tempFilePath;
        }
    }
    
    

    the db file

    <?php 
    
    return [
        'url' => 'mysql://USER:PWD@database:3306/myDatabaseName?serverVersion=10.5.15-MariaDB&charset=utf8mb4'
    ];
    

    The migration file (yaml)

    
    migrations_paths:
            # namespace is arbitrary but should be different from App\Migrations
            # as migrations classes should NOT be autoloaded
            'DoctrineMigrations': '/app/migrations'
    table_storage:
      table_name: 'doctrine_migration_versions'
      version_column_name: 'version'
      version_column_length: '191'