53 votes

Symfony 2 : connexion à une base de données multiple et dynamique

Je suis assez nouveau sur SF2 et je me demandais comment je pourrais gérer les connexions à plusieurs bases de données dans UN bundle. Pour le moment, j'ai cette solution - qui fonctionne bien - mais je ne sais pas si c'est la bonne façon de le faire....

dans myBundle\Ressource\config\config.yml :

doctrine:
dbal:
    default_connection:       default
    connections:
        default:
            dbname:           SERVER
            user:             root
            password:         null
            host:             localhost
        client:
            dbname:           CLIENT_134
            user:             root
            password:         null
            host:             localhost
orm:
    default_entity_manager:   default
    entity_managers:
        default:
            connection:       default
            mappings:
                MyBundle: ~
        client:
            connection:       client
            mappings:
                MyBundle: ~

Et ensuite, pour passer d'une base de données à l'autre, je fais :

$O_ressource=  $this->get('doctrine')->getEntityManager('client');
$O_ressource=  $this->get('doctrine')->getEntityManager('default');

Alors les gars, pensez-vous que c'est une bonne façon de gérer cela?

Et ma deuxième question est :

comment configurer une connexion de base de données dynamique? Je veux dire que j'ai 100 bases de données dans mon système et je ne peux pas toutes les configurer dans mon fichier config.yml. Donc, je voudrais être capable de changer de base de données en vol.

Merci pour votre aide!

0 votes

Par "connexion à la base de données dynamique", voulez-vous dire créer une connexion DBAL depuis votre contrôleur?

3 votes

Oui, exactement! Être capable de passer d'une base de données à une autre, et ces bases de données peuvent ne pas être déclarées dans le fichier config.yml

0 votes

GetEntityManager est obsolète dans la dernière version de Symfony2

53voto

zulus Points 776

Si vous utilisez ConnectionFactory, vos abonnés aux événements attachés à la connexion cesseront de fonctionner, par exemple stofDoctrineExtensions.

Voici ma méthode. Comme avec ConnectionFactory, j'ai une connexion vide et un EntityManager. En travaillant, je remplace simplement la configuration de la connexion par des Réflexions. Fonctionne sur SF 2.0.10 ;)

class YourService extends ContainerAware
{ 

  public function switchDatabase($dbName, $dbUser, $dbPass) 
  {
    $connection = $this->container->get(sprintf('doctrine.dbal.%s_connection', 'dynamic_conn'));
    $connection->close();

    $refConn = new \ReflectionObject($connection);
    $refParams = $refConn->getProperty('_params');
    $refParams->setAccessible('public'); //we have to change it for a moment

    $params = $refParams->getValue($connection);
    $params['dbname'] = $dbName;
    $params['user'] = $dbUser;
    $params['password'] = $dbPass;

    $refParams->setAccessible('private');
    $refParams->setValue($connection, $params);
    $this->container->get('doctrine')->resetEntityManager('dynamic_manager'); // for sure (unless you like broken transactions)
  }
}

MISE À JOUR:

Une solution plus élégante pour doctrine 2.2 / sf 2.3 (sans réflexion), créée pour php5.4 (J'adore l'initialisation de tableau :D) Nous pouvons utiliser la fonctionnalité de doctrine appelée wrapper de connexion, voir http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/portability.html

Cet exemple utilise le service de session pour stocker temporairement les détails de connexion.

Tout d'abord, nous devons créer un wrapper de connexion spécial :

namespace w3des\DoctrineBundle\Connection;

use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\Session\Session;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Event\ConnectionEventArgs;

/*
 * @author Dawid zulus Pakula [zulus@w3des.net]
 */
class ConnectionWrapper extends Connection
{

const SESSION_ACTIVE_DYNAMIC_CONN = 'active_dynamic_conn';

/**
 * @var Session
 */
private $session;

/**
 * @var bool
 */
private $_isConnected = false;

/**
 * @param Session $sess
 */
public function setSession(Session $sess)
{
    $this->session = $sess;
}

public function forceSwitch($dbName, $dbUser, $dbPassword)
{
    if ($this->session->has(self::SESSION_ACTIVE_DYNAMIC_CONN)) {
        $current = $this->session->get(self::SESSION_ACTIVE_DYNAMIC_CONN);
        if ($current[0] === $dbName) {
            return;
        }
    }

    $this->session->set(self::SESSION_ACTIVE_DYNAMIC_CONN, [
        $dbName,
        $dbUser,
        $dbPass
    ]);

    if ($this->isConnected()) {
        $this->close();
    }
}

/**
 * {@inheritDoc}
 */
public function connect()
{
    if (! $this->session->has(self::SESSION_ACTIVE_DYNAMIC_CONN)) {
        throw new \InvalidArgumentException('You have to inject into valid context first');
    }
    if ($this->isConnected()) {
        return true;
    }

    $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array();

    $params = $this->getParams();
    $realParams = $this->session->get(self::SESSION_ACTIVE_DYNAMIC_CONN);
    $params['dbname'] = $realParams[0];
    $params['user'] = $realParams[1];
    $params['password'] = $realParams[2];

    $this->_conn = $this->_driver->connect($params, $params['user'], $params['password'], $driverOptions);

    if ($this->_eventManager->hasListeners(Events::postConnect)) {
        $eventArgs = new ConnectionEventArgs($this);
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
    }

    $this->_isConnected = true;

    return true;
}

/**
 * {@inheritDoc}
 */
public function isConnected()
{
    return $this->_isConnected;
}

/**
 * {@inheritDoc}
 */
public function close()
{
    if ($this->isConnected()) {
        parent::close();
        $this->_isConnected = false;
    }
}
}

Ensuite, enregistrez-le dans votre configuration de doctrine :

…

connections:
  dynamic:
    driver:   %database_driver%
    host:     %database_host%
    port:     %database_port%
    dbname:   'empty_database'
    charset:  UTF8
    wrapper_class: 'w3des\DoctrineBundle\Connection\ConnectionWrapper'

Et notre ConnectionWrapper est correctement enregistré. Maintenant, l'injection de session.

Créez d'abord une classe CompilerPass spéciale :

namespace w3des\DoctrineBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class ConnectionCompilerPass implements CompilerPassInterface
{

/**
 * {@inheritDoc}
 */
public function process(ContainerBuilder $container)
{
    $connection = $container
    ->getDefinition('doctrine.dbal.dynamic_connection')
    ->addMethodCall('setSession', [
        new Reference('session')
    ]);
}
}

Et enregistrons notre nouvelle classe de compilateur dans la classe *Bundle :

public function build(ContainerBuilder $container)
{
    parent::build($container);
    $container->addCompilerPass(new ConnectionCompilerPass());
}

Et c'est tout !

La connexion sera créée à la demande, en fonction des propriétés de session.

Pour changer de base de données, utilisez simplement :

$this->get('doctrine.dbal.dynamic_connection')->forceSwitch($dbname, $dbuser, $dbpass);

Avantages

  1. Plus de réflexion
  2. Création à la demande
  3. Élégant et puissant

Inconvénients

  1. Vous devez nettoyer manuellement votre gestionnaire d'entités, ou créer un événement doctrine spécial pour cela
  2. Beaucoup plus de code

1 votes

Quel est l'objectif de dynamic_conn ou dynamic_manager? Pouvez-vous élaborer davantage sur quel est leur but? Merci

0 votes

J'ai une application, où chaque client a sa propre base de données avec le même schéma que les autres clients. Cette méthode me permet de passer à la base de données réelle en ajustant les privilèges de session, par exemple.

0 votes

Merci pour cette réponse. Avec l'aide de knpuniversity.com/screencast/question-answer-day/…, j'ai pu sélectionner des bases de données en fonction du sous-domaine donné

23voto

Problematic Points 10229

Vous pouvez regarder dans Symfony\Bundle\DoctrineBundle\ConnectionFactory, en utilisant le service de conteneur doctrine.dbal.connection_factory:

$connectionFactory = $this->container->get('doctrine.dbal.connection_factory');
$connection = $connectionFactory->createConnection(array(
    'driver' => 'pdo_mysql',
    'user' => 'root',
    'password' => '',
    'host' => 'localhost',
    'dbname' => 'foo_database',
));

C'est juste un exemple rapide, mais cela devrait vous mettre sur la bonne voie.

0 votes

Merci, cela m'a aidé! Je me demandais s'il y a un moyen de changer la valeur d'un paramètre défini dans l'un de mes fichiers de configuration? Par exemple : # app/config/config.yml parameters: my_mailer.class: Acme\HelloBundle\Mailer my_mailer.transport: sendmail services: my_mailer: class: %my_mailer.class% arguments: [%my_mailer.transport%] Puis-je changer la valeur de : %my_mailer.transport%

2 votes

J'ai réussi à créer une connexion "sur le pouce" en définissant une entité qui est associée à un élément de connexion fake dbal.connection - dans mon fichier config.yml - puis j'utilise le doctrine.dbal.connection_factory pour créer la bonne connexion et enfin je définis cette nouvelle connexion à $this->container $this->container->set('doctrine.dbal.custom_client_connectio‌​n', $connection); $myObject = $this->get('doctrine') ->getEntityManager('custom_client') ->getRepository('FooBarBundle:MyObject) ->find($id); Je ne sais pas si c'est la bonne façon / la meilleure façon, mais ça fonctionne

0 votes

Il semble qu'en Symfony 2.1, vous ne pouvez pas accéder directement à la propriété du conteneur, vous devez utiliser la méthode d'aide $this->getContainer()->get('...')

0voto

bal3no Points 1

Je rencontre le même besoin d'avoir différentes bases de données avec le même schéma pour chaque client. Depuis symfony 2.3, après la dépréciation de la méthode resetEntityManager, j'ai remarqué que le code fonctionne bien sans fermer la connexion et sans réinitialiser le Manager (ancien Entity).

Voici mon code actuel en fonctionnement :

public function switchDatabase($dbName, $dbUser, $dbPass) {
    $connection = $this->container->get(sprintf('doctrine.dbal.%s_connection', 'dynamic_conn'));

    $refConn = new \ReflectionObject($connection);
    $refParams = $refConn->getProperty('_params');
    $refParams->setAccessible('public'); //nous devons le changer pour un instant

    $params = $refParams->getValue($connection);
    $params['dbname'] = $dbName;
    $params['user'] = $dbUser;
    $params['password'] = $dbPass;

    $refParams->setAccessible('private');
    $refParams->setValue($connection, $params);
}

0 votes

Je construis un système de tableau de bord qui intégrera différentes bases de données en une seule, offrant ainsi une interface pour gérer ces bases de données avec Symfony3.0. La chose est que Doctrine utilise le fichier parameters.yml pour déclarer les connexions et je n'ai vu personne parler de comment réaliser la même chose que ce qui est contenu dans ce fil dans cette perspective. Les docs ne sont pas claires non plus car je les ai essayées et je continue de tomber sur des erreurs. Quelqu'un pourrait-il fournir une solution pour Symfony3.0 s'il vous plaît.... Merci.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X