Accueil /  Blog / Symfony / Travailler avec plusieurs connexions Doctrine

Travailler avec plusieurs connexions Doctrine

Publié le jeudi 5 octobre 2017

Astuces lorsqu'on travaille avec plusieurs connexions Doctrine dans un seul et même projet.

Travailler avec plusieurs bases de données dans un même projet n'arrive pas tous les jours.

Ce billet trace quelques astuces qui vous permettront probablement de gagner un peu de temps lorsque vous y serez confrontés.

Déclarer qui s'occupe de qui

C'est la première chose à faire lorsque vous utilisez plusieurs sources de données : dire à Doctrine sur quelle connexion il est censé mapper vos entités.

C'est ce que fait pour vous l'auto_mapping lorsque vous n'avez qu'une seule base de données.

Toutes les entités d'un même bundle ne pourront être gérées que par une seule connexion. Une connexion peut gérer les entités de plusieurs bundles.

Structure des bundles et des entités :

AppBundle
    > Entity
        > Product.php

WarehouseBundle
    > Entity
        > Stock.php
doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                # ...
            warehouse:
                # ...
    orm:
        default_entity_manager: default
        entity_managers:
            default:
                connection: default
                mappings:
                    AppBundle: ~
            external:
                connection: warehouse
                mappings:
                    WarehouseBundle: ~

Récupérer le bon manager

Même si vous pouvez demander le manager en direct, en connaissant la façon dont l'id du service est construit par Symfony (doctrine.orm.{name}_entity_manager), c'est une pratique fortement déconseillée, car elle ne tient pas compte de la configuration.

Le registre doctrine permet d'abstraire cette configuration.

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Product;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Warehouse\Entity\Stock;

class CartController extends Controller
{
    public function indexAction()
    {
        $productManager = $this->getDoctrine()->getManagerForClass(Product::class);
        $productRepository = $this->getDoctrine()->getRepository(Product::class);

        $stockManager = $this->getDoctrine()->getManagerForClass(Stock::class);
        $stockRepository = $this->getDoctrine()->getRepository(Stock::class);
    }
}

Event listeners/subscribers sélectifs

Lorsque vous enregistrez un event listener/subscriber Doctrine, il est actif pour toutes les connections.

Ce qui peut poser problème si vous faites ce genre choses :

services:
    app.event_listener.updated_at:
        class: AppBundle\Event\Listener\UpdatedAtListener
        tags:
            - { name: doctrine.event_listener, event: onFlush }
<?php

namespace AppBundle\Event\Listener;

use AppBundle\Entity\Product;
use Doctrine\ORM\Event\OnFlushEventArgs;

class UpdatedAtListener
{
    public function onFlush(OnFlushEventArgs $eventArgs)
    {
        $entityManager = $eventArgs->getEntityManager();

        $products = $entityManager->getRepository(Product::class)->findAll();
    }
}
<?php
$stock = $stockRepository->findForProduct($product);
$stock->setValue($stock->getValue() - 10);
$stockManager->flush();

Le service UpdatedAtListener s'attend à faire quelque chose sur les produits lors du flush. Mais il a été enregistré pour toutes les connexions.

Dans ce cas précis, $stockRepository n'a aucune connaissance de l'entité Product, une exception sera levée pour cela.

La solution est simple, il suffit de déclarer que l'event listener n'est valable que pour la connexion default.

services:
    app.event_listener.updated_at:
        class: AppBundle\Event\Listener\UpdatedAtListener
        tags:
            - { name: doctrine.event_listener, event: onFlush, connection: default }
Suivez notre actualité en avant première. Pas plus d’une newsletter par mois.