Sake
Sake

Reputation: 1

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.

Upvotes: 0

Views: 119

Answers (1)

Sake
Sake

Reputation: 1

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'

Upvotes: 0

Related Questions