symfony2 动态数据库连接
我的 symfony2 项目有一个主数据库和许多子数据库.每个子数据库都是为每个用户创建的,数据库凭据存储在主数据库中.当用户登录时,从主数据库获取用户特定的数据库凭据,理想情况下应该建立子数据库连接.我用谷歌搜索了同样的东西,我遇到了许多解决方案,最后做了以下事情:
My symfony2 project has a main database and many child databases. Each child database is created for each user, the database credentials are stored in the main database. When the user logins, the user specific database credentials are fetched from the main database and the child database connection ideally should be established. I googled for the same, and I came accross a number of solutions and finally did the following:
#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
我创建了一个连接到主数据库的默认连接和一个用于子数据库的空连接,同样我创建了实体管理器.然后我创建了默认事件侦听器并将以下代码添加到onKernelRequest"中:
I created a default connection to connect to the main database and an empty connection for the child database, similarly I created entity managers. Then I created default event listener and added the following code to the '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');
....
}
以上代码设置子数据库参数并重置dynamic_em实体管理器.
The above code sets the child database parameters and resets the dynamic_em entity manager.
当我在某个控制器中执行以下操作时,它工作正常,并且数据是从子数据库中获取的.
When I do the following in some controller, it works fine and the data if fetched from the child database.
$getblog= $em->getRepository('BloggerBlogBundle:Blog')->findById($id); //uses doctrine
但是,当我使用以下代码中的安全上下文时,我收到错误NO DATABASE SELECTED".
But, when I use security context as seen in the following code, I get an error 'NO DATABASE SELECTED'.
$securityContext = $this->container->get('security.context');
$loggedinUserid = $securityContext->getToken()->getUser()->getId();
如何动态设置数据库连接并使用安全上下文?
更新:-
经过长时间的反复试验和谷歌搜索,我意识到在执行 onKernelRequest
之前设置了 security.context
.现在的问题是如何将数据库连接细节注入security.context,以及在哪里注入?
After much time spent on trial and error, and googling around, I realized that security.context
is set before the execution of onKernelRequest
. Now the question is how to inject the database connection details into the security.context, and where to inject?
我们需要设置 DBAL 和安全上下文并创建安全令牌,然后我们才能操作数据库连接详细信息.
We need to get to a point where the DBAL and security context is set and security token is created, and we can manipulate database connection details.
因此,正如以下链接中的人所说,我对代码进行了更改,这正是我想要做的.http://forum.symfony-project.org/viewtopic.php?t=37398&p=124413
Hence, as the person in the following link stated, I made changes to my code, as thats exactly what I would want to do. http://forum.symfony-project.org/viewtopic.php?t=37398&p=124413
这让我将以下代码添加到我的项目中:
That leaves me the following code add to my project:
#config.yml //remains unchanged, similar to above code
编译器pass创建如下:
A compiler pass is created as follows:
// src/Blogger/BlogBundle/BloggerBlogBundle.php
namespace BloggerBlogBundle;
use SymfonyComponentHttpKernelBundleBundle;
use SymfonyComponentDependencyInjectionContainerBuilder;
use BloggerBlogBundleDependencyInjectionCompilerCustomCompilerPass;
class BloggerBlogBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new CustomCompilerPass());
}
}
编译器pass如下:
# 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'] = 'BloggerBlogBundleUserDependentMySqlDriver';
$args[0]['driverOptions'][] = array(new Reference('security.context'));
$def->replaceArgument(0, $args[0]);
}
}
}
驱动类代码如下:
# src/Blogger/BlogBundle/UserDependentMySqlDriver.php
use DoctrineDBALDriverPDOMySqlDriver;
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());
}
}
以上代码已添加到我的项目中,我认为这是解决我的问题的实际工作.
The above code were added to my project, and I assume that this is the actual work around for to my problem.
但现在我收到以下错误:
But now I get the following error:
ServiceCircularReferenceException:检测到循环引用服务security.context",路径:profiler_listener -> profiler ->security.context -> security.authentication.manager ->fos_user.user_provider.username_email -> fos_user.user_manager ->学说.orm.dynamic_manager_entity_manager ->学说.dbal.dynamic_conn_connection".
ServiceCircularReferenceException: Circular reference detected for service "security.context", path: "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".
我怎样才能让我的代码工作?我敢打赌,我在这里做错了什么,我将不胜感激任何提示和帮助.
How, can I get my code to work? I bet that I am doing something wrong here and I would appreciate any hints and help.
推荐答案
这里,你需要在你自己的业务中实现你自己的逻辑.
Here, you need to implement your own logic on your own, in your own business.
查看关于如何创建实体管理器"的 Doctrine 文档.
Take a look at documentation of Doctrine on "how to create an entity manager".
然后创建一个具有清晰 API 的服务:
Then create a service with a clear API:
$this->get('em_factory')->getManager('name-of-my-client'); // returns an EntityManager
你不能用默认的 DoctrineBundle 来做,它不能用于动态特性.
You can't do it with default DoctrineBundle, it's not usable for dynamic features.
class EmFactory
{
public function getManager($name)
{
// you can get those values:
// - autoguess, based on name
// - injection through constructor
// - other database connection
// just create constructor and inject what you need
$params = array('username' => $name, 'password' => $name, ....);
// get an EM up and running
// see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html#obtaining-the-entitymanager
return $em;
}
}
并声明为服务.
相关文章