Je suis assez nouveau sur SF2 et je me demandais comment gérer les connexions aux différentes bases de données en un seul paquet. 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 puis, pour passer à l'un des BD ou à 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 les définir toutes dans mon fichier config.yml. Je voudrais donc pouvoir changer de base de données à la volée.
Merci pour l'aide!
Si vous utilisez ConnectionFactory
, vos abonnés à l'événement connectés à la connexion cesseront de fonctionner, par exemple stofDoctrineExtensions.
Voici ma méthode. J'ai comme avec ConnectionFactory
j'ai une connexion vide et EntityManager. Tout en travaillant, je remplace simplement la configuration de la connexion par Reflections. 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 [~ # ~] :
Solution plus élégante pour doctrine 2.2/sf 2.3 (sans relection), créée pour php5.4 (J'adore le nouveau tableau initialiseur: D) Nous pouvons utiliser doctrine fonctionnalité 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 la connexion.
Au début, 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 [[email protected]]
*/
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;
}
}
}
Enregistrez-le ensuite dans votre configuration 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 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 nous 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 la session.
Pour changer de base de données, utilisez simplement:
$this->get('doctrine.dbal.dynamic_connection')->forceSwitch($dbname, $dbuser, $dbpass);
Avantages
Inconvénients
Vous pouvez consulter 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',
));
Ce n'est qu'un exemple rapide, mais cela devrait vous aider à démarrer.
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 fonctionnait bien sans fermer la connexion et sans réinitialiser le (ancienne entité) Manager.
c'est mon code de travail actuel:
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'); //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);
}