doctrine / migrations

Doctrine Database Migrations Library
https://www.doctrine-project.org/projects/migrations.html
MIT License
4.68k stars 385 forks source link

[Command] Calling `DoctrineCommand` multiple times with multiple connections #1345

Open cavasinf opened 1 year ago

cavasinf commented 1 year ago

Bug Report

Q A
BC Break no
Version 3.6.0

Summary

Using Multiple Database in a single Symfony Application. We need to trigger the MigrateCommand x times for x databases from a cron/hook when deploying a new version.

Current behavior

Note : Running this Command with the --conn option seems to not works. So we use the --em option.

When calling the MigrateCommand multiple times FROM Command/Code the connection will be:

  1. getted https://github.com/doctrine/migrations/blob/2f4b14968df1cb1c6e8beb724d142c4a8ddf2842/lib/Doctrine/Migrations/Tools/Console/Command/MigrateCommand.php#L132

  2. AND setted at the same time (if undefined) https://github.com/doctrine/migrations/blob/2f4b14968df1cb1c6e8beb724d142c4a8ddf2842/lib/Doctrine/Migrations/DependencyFactory.php#L158-L168

Because of this, the connection will always be the first "implemented". In the end, calling this command in a loop and changing the connection/entityManager at each iteration won't work as untended.

How to reproduce

  1. Have multiple connections
  2. Have multiple EntityManagers
  3. Run this command
    
    <?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface;

[AsCommand(

name: 'app:test',

)] class TestCommand extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { $entityManagers = [ 'client1', 'client2', ];

    foreach ($entityManagers as $entityManager) {
        $commandToCall = $this->getApplication()->find('d:m:m');
        $commandInput  = new ArrayInput(
            [
                '--em' => $entityManager,
            ]
        );

        $commandToCall->run(
            $commandInput,
            $output
        );
    }

    return Command::SUCCESS;
}

}

4. Output
   a. First loop :  ` WARNING! You are about to execute a migration in database "client1" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]`
   b. Second loop: ` WARNING! You are about to execute a migration in database "client1" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]`

#### Expected behavior

The first loop should be `client1` and the second `client2`.

> Note : Running single command from CLI is working
> `php bin/console d:m:m --em=client1`
> or `php bin/console d:m:m --em=client2`

#### Doctrine config

<details>
  <summary>php bin/console debug:config doctrine</summary>

  ```bash
Current configuration for extension with alias "doctrine"
=========================================================

doctrine:
    orm:
        resolve_target_entities:
            Allsoftware\SymfonyBundle\Model\FichierInterface: App\Entity\Fichier
            Allsoftware\SymfonyBundle\Model\DossierInterface: App\Entity\Dossier
        auto_generate_proxy_classes: true
        entity_managers:
            default:
                dql:
                    string_functions:
                        regexp: DoctrineExtensions\Query\Mysql\Regexp
                    numeric_functions: {  }
                    datetime_functions: {  }
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                auto_mapping: true
                mappings:
                    App:
                        is_bundle: false
                        type: attribute
                        dir: /var/www/html/src/Entity
                        prefix: App\Entity
                        alias: App
                        mapping: true
                    AllsoftwareSymfonyBundle:
                        type: attribute
                        dir: src/Model
                        prefix: Allsoftware\SymfonyBundle\Model
                        mapping: true
                query_cache_driver:
                    type: null
                result_cache_driver:
                    type: null
                class_metadata_factory_name: Doctrine\ORM\Mapping\ClassMetadataFactory
                default_repository_class: Doctrine\ORM\EntityRepository
                quote_strategy: doctrine.orm.quote_strategy.default
                entity_listener_resolver: null
                repository_factory: doctrine.orm.container_repository_factory
                schema_ignore_classes: {  }
                report_fields_where_declared: false
                validate_xml_mapping: false
                hydrators: {  }
                filters: {  }
            client1:
                dql:
                    string_functions:
                        regexp: DoctrineExtensions\Query\Mysql\Regexp
                    numeric_functions: {  }
                    datetime_functions: {  }
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                mappings:
                    App:
                        is_bundle: false
                        type: attribute
                        dir: /var/www/html/src/Entity
                        prefix: App\Entity
                        alias: App
                        mapping: true
                connection: client1
                query_cache_driver:
                    type: null
                result_cache_driver:
                    type: null
                class_metadata_factory_name: Doctrine\ORM\Mapping\ClassMetadataFactory
                default_repository_class: Doctrine\ORM\EntityRepository
                auto_mapping: false
                quote_strategy: doctrine.orm.quote_strategy.default
                entity_listener_resolver: null
                repository_factory: doctrine.orm.container_repository_factory
                schema_ignore_classes: {  }
                report_fields_where_declared: false
                validate_xml_mapping: false
                hydrators: {  }
                filters: {  }
            client2:
                dql:
                    string_functions:
                        regexp: DoctrineExtensions\Query\Mysql\Regexp
                    numeric_functions: {  }
                    datetime_functions: {  }
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                mappings:
                    App:
                        is_bundle: false
                        type: attribute
                        dir: /var/www/html/src/Entity
                        prefix: App\Entity
                        alias: App
                        mapping: true
                connection: client2
                query_cache_driver:
                    type: null
                result_cache_driver:
                    type: null
                class_metadata_factory_name: Doctrine\ORM\Mapping\ClassMetadataFactory
                default_repository_class: Doctrine\ORM\EntityRepository
                auto_mapping: false
                quote_strategy: doctrine.orm.quote_strategy.default
                entity_listener_resolver: null
                repository_factory: doctrine.orm.container_repository_factory
                schema_ignore_classes: {  }
                report_fields_where_declared: false
                validate_xml_mapping: false
                hydrators: {  }
                filters: {  }
        enable_lazy_ghost_objects: false
        proxy_dir: /var/www/html/var/cache/dev/doctrine/orm/Proxies
        proxy_namespace: Proxies
        controller_resolver:
            enabled: true
            auto_mapping: true
            evict_cache: false
    dbal:
        connections:
            default:
                url: '%env(resolve:DATABASE_URL)%'
                wrapper_class: App\DBAL\MultiDbConnectionWrapper
                driver: pdo_mysql
                logging: true
                profiling: true
                profiling_collect_backtrace: false
                profiling_collect_schema_errors: true
                options: {  }
                mapping_types: {  }
                default_table_options: {  }
                schema_manager_factory: doctrine.dbal.legacy_schema_manager_factory
                slaves: {  }
                replicas: {  }
            client1:
                url: 'mysql://root:root@mysqlhost:3306/nils_client1'
                driver: pdo_mysql
                logging: true
                profiling: true
                profiling_collect_backtrace: false
                profiling_collect_schema_errors: true
                options: {  }
                mapping_types: {  }
                default_table_options: {  }
                schema_manager_factory: doctrine.dbal.legacy_schema_manager_factory
                slaves: {  }
                replicas: {  }
            client2:
                url: 'mysql://root:root@mysqlhost:3306/nils_client2'
                driver: pdo_mysql
                logging: true
                profiling: true
                profiling_collect_backtrace: false
                profiling_collect_schema_errors: true
                options: {  }
                mapping_types: {  }
                default_table_options: {  }
                schema_manager_factory: doctrine.dbal.legacy_schema_manager_factory
                slaves: {  }
                replicas: {  }
        types:
            uuid:
                class: Ramsey\Uuid\Doctrine\UuidType
        driver_schemes: {  }