Conexão de banco de dados dinâmico symfony2
Meu projeto symfony2 possui um banco de dados principal e muitos bancos de dados filho. Cada banco de dados filho é criado para cada usuário, as credenciais do banco de dados são armazenadas no banco de dados principal. Quando o usuário faz login, as credenciais do banco de dados específicas do usuário são buscadas no banco de dados principal e a conexão do banco de dados filho deve ser estabelecida de maneira ideal. Eu pesquisei o mesmo, e cheguei a várias soluções e finalmente fiz o seguinte:
#config.yml
doctrine:
dbal:
default_connection: default
connections:
default:
dbname: maindb
user: root
password: null
host: localhost
dynamic_conn:
dbname: ~
user: ~
password: ~
host: localhost
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
auto_mapping: true
dynamic_em:
connection: dynamic_conn
auto_mapping: true
Eu criei uma conexão padrão para se conectar ao banco de dados principal e uma conexão vazia para o banco de dados filho, da mesma forma eu criei os gerenciadores de entidades. Em seguida, criei o ouvinte de eventos padrão e adicionei o seguinte código ao 'onKernelRequest':
public function onKernelRequest(GetResponseEvent $event) //works like preDispatch in Zend
{
//code to get db credentials from master database and stored in varaiables
....
$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_em');
....
}
O código acima define os parâmetros do banco de dados filho e redefine o gerenciador de entidades dynamic_em.
Quando eu faço o seguinte em algum controlador, ele funciona bem e os dados são buscados no banco de dados filho.
$getblog= $em->getRepository('BloggerBlogBundle:Blog')->findById($id); //uses doctrine
Mas, quando eu uso o contexto de segurança como visto no código a seguir, recebo um erro 'NO DATABASE SELECTED'.
$securityContext = $this->container->get('security.context');
$loggedinUserid = $securityContext->getToken()->getUser()->getId();
Como posso definir a conexão com o banco de dados dinamicamente e usar o contexto de segurança também?
ATUALIZAR:-
Depois de muito tempo gasto em tentativa e erro, e googling ao redor, percebi quesecurity.context
é definido antes da execução deonKernelRequest
. Agora a questão écomo para injetar os detalhes da conexão do banco de dados no security.context eOnde injectar?
Precisamos chegar a um ponto em que o contexto de segurança e DBAL esteja definido e o token de segurança seja criado, e podemos manipular os detalhes da conexão com o banco de dados.
Assim, como a pessoa no link a seguir indicou, eu fiz alterações no meu código, como isso é exatamente o que eu gostaria de fazer.http://forum.symfony-project.org/viewtopic.php?t=37398&p=124413
Isso me deixa o seguinte código adicionar ao meu projeto:
#config.yml //remains unchanged, similar to above code
Um passo do compilador é criado da seguinte forma:
// src/Blogger/BlogBundle/BloggerBlogBundle.php
namespace Blogger\BlogBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Blogger\BlogBundle\DependencyInjection\Compiler\CustomCompilerPass;
class BloggerBlogBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new CustomCompilerPass());
}
}
A passagem do compilador é a seguinte:
# src/Blogger/BlogBundle/DependencyInjection/Compiler/CustomCompilerPass.php
class CustomCompilerPassimplements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$connection_service = 'doctrine.dbal.dynamic_conn_connection';
if ($container->hasDefinition($connection_service))
{
$def = $container->getDefinition($connection_service);
$args = $def->getArguments();
$args[0]['driverClass'] = 'Blogger\BlogBundle\UserDependentMySqlDriver';
$args[0]['driverOptions'][] = array(new Reference('security.context'));
$def->replaceArgument(0, $args[0]);
}
}
}
O código da classe do driver é o seguinte:
# src/Blogger/BlogBundle/UserDependentMySqlDriver.php
use Doctrine\DBAL\Driver\PDOMySql\Driver;
class UserDependentMySqlDriver extends Driver
{
public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
{
$dbname = ..... //store database name in variable
$params['dbname'] = $dbname;
return parent::connect($params, $username, $password, array());
}
}
O código acima foi adicionado ao meu projeto, e eu suponho que este é o trabalho real para o meu problema.
Mas agora recebo o seguinte erro:
ServiceCircularReferenceException: Referência circular detectada para o serviço "security.context", caminho: "profiler_listener -> profiler -> security.context -> security.authentication.manager -> fos_user.user_provider.username_email -> fos_user.user_manager -> doctrine.orm. dynamic_manager_entity_manager -> doctrine.dbal.dynamic_conn_connection ".
Como faço para que meu código funcione? Aposto que estou fazendo algo errado aqui e gostaria de receber sugestões e ajuda.