简体   繁体   English

动态数据库连接symfony2

[英]Dynamic database connection symfony2

My symfony2 project has a main database and many child databases. 我的symfony2项目有一个主数据库和许多子数据库。 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

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': 然后我创建了默认事件监听器并将以下代码添加到'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');
    ....
}

The above code sets the child database parameters and resets the dynamic_em entity manager. 上面的代码设置子数据库参数并重置dynamic_em实体管理器。

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

But, when I use security context as seen in the following code, I get an error 'NO DATABASE SELECTED'. 但是,当我使用安全上下文时,如下面的代码所示,我收到错误'NO DATABASE SELECTED'。

$securityContext = $this->container->get('security.context');
$loggedinUserid = $securityContext->getToken()->getUser()->getId();

How can I set database connection dynamically and use security context as well? 如何动态设置数据库连接并使用安全上下文?

UPDATE:- 更新: -

After much time spent on trial and error, and googling around, I realized that security.context is set before the execution of onKernelRequest . 花了很多时间在试验和错误上,并在谷歌搜索,我意识到security.context是在执行onKernelRequest之前设置的。 Now the question is how to inject the database connection details into the security.context, and where to inject? 现在的问题是如何注入数据库连接细节到security.context,并注入?

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. 我们需要设置DBAL和安全上下文并创建安全令牌,我们可以操作数据库连接详细信息。

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 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

A compiler pass is created as follows: 编译器传递创建如下:

// 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());
    }
}

The compiler pass is as follows: 编译器传递如下:

# 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]);
        }
   }
}

The driver class code is as follows: 驱动程序类代码如下:

# 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());
    }
}

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: 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". ServiceCircularReferenceException:检测到服务“security.context”的循环引用,路径:“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. 在这里,您需要在自己的业务中自己实现自己的逻辑。

Take a look at documentation of Doctrine on "how to create an entity manager". 在“如何创建实体管理器”上查看Doctrine的文档。

Then create a service with a clear API: 然后使用明确的API创建服务:

$this->get('em_factory')->getManager('name-of-my-client'); // returns an EntityManager

You can't do it with default DoctrineBundle, it's not usable for dynamic features. 你不能使用默认的DoctrineBundle,它不能用于动态功能。

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;
    }
}

And declare as a service. 并声明为服务。

I'd like to propose different solution to your original problem.You can use PhpFileLoader to dynamically define parameters for your config.yml. 我想为您的原始问题提出不同的解决方案。您可以使用PhpFileLoader动态定义config.yml的参数。

  1. Extract you main database connection parameters into separate file: 将主数据库连接参数提取到单独的文件中:

     # src/Blogger/BlogBundle/Resources/config/parameters.yml parameters: main_db_name: maindb main_db_user: root main_db_password: null main_db_host: localhost 
  2. Create new PHP script (say DynamicParametersLoader.php) which will inject new parameters in app container. 创建新的PHP脚本(比如DynamicParametersLoader.php),它将在app容器中注入新参数。 I think you cannot use your symfony app in this script, but you can read main db credentials from $container variable. 我认为您不能在此脚本中使用您的symfony应用程序,但您可以从$ container变量中读取主数据库凭据。 Like the following: 如下:

     # src/Blogger/BlogBundle/DependecyInjection/DynamicParametersLoader.php <?php $mainDbName = $container->getParameter('main_db_name'); $mainDbUser = $container->getParameter('main_db_user'); $mainDbPassword = $container->getParameter('main_db_password'); $mainDbHost = $container->getParameter('main_db_host'); # whatever code to query your main database for dynamic DB credentials. You cannot use your symfony2 app services here, so it ought to be plain PHP. ... # creating new parameters in container $container->setParameter('dynamic_db_name', $dbName); $container->setParameter('dynamic_db_user', $dbUser); $container->setParameter('dynamic_db_password', $dbPass); 
  3. Now you need to tell Symfony about your script and new parameters.yml file: 现在你需要告诉Symfony你的脚本和新的parameters.yml文件:

     # config.yml imports: - { resource: parameters.yml } - { resource: ../../DependencyInjection/DynamicParametersLoader.php } 
  4. At this step you can freely use injected parameters in you config: 在此步骤中,您可以在配置中自由使用注入的参数:

     # config.yml ... dynamic_conn: dbname: %dynamic_db_name% user: %dynamic_db_user% password: %dynamic_db_password% ... 

There is a very good solution using an event listener posted here: 使用此处发布的事件监听器有一个非常好的解决方案:

Symfony2, Dynamic DB Connection/Early override of Doctrine Service Symfony2,动态数据库连接/ Doctrine服务的早期覆盖

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM