Динамическое подключение к базе данных symfony2

В моем проекте symfony2 есть основная база данных и множество дочерних баз данных. Каждая дочерняя база данных создается для каждого пользователя, учетные данные базы данных хранятся в основной базе данных. Когда пользователь входит в систему, пользовательские учетные данные для базы данных извлекаются из основной базы данных и идеальное соединение с дочерней базой. Я тоже искал Google, и я пришел к ряду решений и, наконец, сделал следующее:

#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':

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.

Когда я делаю следующее на каком-то контроллере, он отлично работает и данные извлекаются из дочерней базы данных.

$getblog= $em->getRepository('BloggerBlogBundle:Blog')->findById($id); //uses doctrine

Но когда я использую контекст безопасности, как показано в следующем коде, я получаю сообщение об ошибке "NO DATABASE SELECTED".

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

Как установить динамическое подключение к базе данных и использовать контекст безопасности?

UPDATE: -

После долгого времени, затраченного на проб и ошибок, и поиска в Google, я понял, что security.context устанавливается перед выполнением onKernelRequest. Теперь вопрос как ввести данные подключения к базе данных в файл security.context и , где, чтобы ввести?

Нам нужно дойти до точки, в которой установлен DBAL и контекст безопасности, и маркер безопасности создается, и мы можем манипулировать деталями подключения к базе данных.

Следовательно, как сказал человек в следующей ссылке, я вносил изменения в свой код, так как это именно то, что я хотел бы сделать. http://forum.symfony-project.org/viewtopic.php?t=37398&p=124413

Это оставляет мне следующий код, добавляемый в мой проект:

#config.yml //remains unchanged, similar to above code

Протокол компилятора создается следующим образом:

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

Протокол компилятора выглядит следующим образом:

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

Код класса драйвера выглядит следующим образом:

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

Вышеприведенный код был добавлен в мой проект, и я предполагаю, что это фактическая работа для моей проблемы.

Но теперь я получаю следующую ошибку:

ServiceCircularReferenceException: обнаружена циркулярная ссылка для service "security.context", путь: "profiler_listener → профайлер → 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".

Как я могу заставить мой код работать? Готов поспорить, что я делаю что-то неправильно здесь, и я был бы признателен за любые подсказки и помощь.

Ответ 1

Здесь вам нужно реализовать свою собственную логику самостоятельно, в своем собственном бизнесе.

Взгляните на документацию Doctrine на "как создать диспетчер сущностей".

Затем создайте службу с понятным API:

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

Вы не можете сделать это с помощью 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;
    }
}

И объявить как услугу.

Ответ 2

Я хотел бы предложить другое решение вашей исходной проблемы. Вы можете использовать PhpFileLoader для динамического определения параметров для вашего config.yml.

  • Извлеките основные параметры подключения базы данных в отдельный файл:

    # src/Blogger/BlogBundle/Resources/config/parameters.yml
    
    parameters:
        main_db_name:           maindb
        main_db_user:           root
        main_db_password:       null
        main_db_host:           localhost
    
  • Создайте новый PHP script (скажем, DynamicParametersLoader.php), который добавит новые параметры в контейнер приложения. Я думаю, вы не можете использовать свое приложение symfony в этом script, но вы можете прочитать основные учетные данные db из переменной $container. Как показано ниже:

    # 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);
    
  • Теперь вам нужно сообщить Symfony о своем script и новом файле parameters.yml:

    # config.yml
    imports:
        - { resource: parameters.yml }
        - { resource: ../../DependencyInjection/DynamicParametersLoader.php }
    
  • На этом этапе вы можете свободно использовать введенные параметры в config:

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