Symfony: Forzar a una entidad a utilizar una conexión de base de datos diferente a la configurada por defecto

No es raro tener múltiples bases de datos para un solo proyecto. Normalmente, cuando se usan dos bases de datos, las entidades pueden organizarse en directorios específicos que se declaran en tu configuración. En este caso, el sistema utilizará la conexión configurada para el directorio de la entidad. Puedes ver un ejemplo de múltiples conexiones aquí Symfony 4 / Sonata: gestionando una interfaz de administración multi-servidor

Supongamos que queremos apuntar a una base de datos de solo lectura (una réplica) cada vez que utilicemos una entidad. Habría varias maneras de proceder.

Comencemos por configurar múltiples configuraciones.

DATABASE_MASTER=mysql://login:password@db_master.host:3306/db_master?serverVersion=mariadb-10.10.2
DATABASE_SLAVE=mysql://login:password@db_slave.host:3306/db_slave?serverVersion=mariadb-10.10.2
doctrine:
    dbal:
        default_connection: db_master
        types:
            json: Sonata\Doctrine\Types\JsonType   
        connections:
            db_master:
                url: '%env(resolve:DATABASE_MASTER)%'
                driver: 'pdo_mysql'
                server_version: '5.7'
                charset: utf8
 
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_unicode_ci
                     
            db_slave:
                url: '%env(resolve:DATABASE_SLAVE)%'
                driver: 'pdo_mysql'
                server_version: '5.7'
                charset: utf8
 
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_unicode_ci
    orm:
        auto_generate_proxy_classes: true
        default_entity_manager: default
        entity_managers:
            default:
                connection: db_master
                naming_strategy: doctrine.orm.naming_strategy.underscore
                auto_mapping: true
                mappings:
                    ApplicationSonataMediaBundle: ~
                    SonataMediaBundle: ~
                    Main:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity'
                        prefix: 'App\Entity\'
                        alias: Main       
              
            slave:
                connection: db_slave
                naming_strategy: doctrine.orm.naming_strategy.underscore
                auto_mapping: false
                mappings:
                    delef:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity'
                        prefix: 'App\Entity'
                        alias: delef

Para apuntar a una conexión específica, podríamos modificar nuestro código y apuntar a nuestra conexión para cada solicitud de Doctrine.

Aquí ya hay dos métodos para apuntar a nuestro servidor esclavo para cada solicitud:

<?php

namespace App\Repository;

use App\Entity\Whatever;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityRepository;
use Psr\Container\ContainerInterface;



class WhateverRepository extends ServiceEntityRepository
{
        public function __construct(ContainerInterface $container)
        {
             $this->container = $container;
        }

        public function selectSlave()
        {
            return $this->container->get('doctrine')
            ->getManager('slave')
            ->createQueryBuilder('w')
            ->andWhere('w.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('w.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
        }
}

O de nuevo:

<?php

namespace App\Repository;

use App\Entity\Whatever;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;



class WhateverRepository extends ServiceEntityRepository
{
        public function __construct(ManagerRegistry $registry)
        {
             $this->registry = $registry;
             
        }

        public function selectSlave()
        {
            return $this->registry->
            ->getManager('slave')
            ->createQueryBuilder('w')
            ->andWhere('w.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('w.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
        }
}

Pero esto implica modificar todos nuestros métodos uno por uno para apuntar a nuestra conexión.

Un método más global consiste en definir nuestra conexión en nuestro constructor para todo nuestro repositorio.

<?php

namespace App\Repository;

use App\Entity\Whatever;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityRepository;


class WhateverRepository extends ServiceEntityRepository
{
        public function __construct(ManagerRegistry $registry)
        {
             $this->registry = $registry;
             $manager = $registry->getManager('slave');
             parent::__construct($registry, Whatever::class);
             EntityRepository::__construct(
                          $manager, 
                          $manager->getClassMetadata(Whatever::class)
                          );
        }

        public function selectSlave()
        {
            return $this->createQueryBuilder('w')
            ->andWhere('w.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('w.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
        }
}


Al final, puedes elegir entre los diferentes métodos disponibles según las necesidades de tu aplicación.