Symfony 2: множественное и динамическое соединение с базой данных

50

Я новичок в SF2, и мне было интересно, как я могу управлять соединениями с базами данных с числовыми данными в ONE bundle. На данный момент у меня есть это решение, которое отлично работает, но я не знаю, правильно ли это сделать.

в 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: ~

И затем, чтобы переключиться на один из BD или другого, я делаю:

$O_ressource=  $this->get('doctrine')->getEntityManager('client');
$O_ressource=  $this->get('doctrine')->getEntityManager('default');

Итак, ребята, как вы думаете, это хороший способ справиться с этим?

И мой второй вопрос:

как настроить динамическое подключение к базе данных? Я имею в виду, что у меня есть 100 баз данных в моей системе, и я не могу установить их все в файле config.yml. Поэтому я хотел бы иметь возможность изменять базу данных на лету.

Спасибо за помощь!

  • 0
    Под «динамическим соединением с базой данных» вы подразумеваете создание соединения DBAL с вашего контроллера?
  • 3
    Да, точно! Возможность переключения с одной базы данных на другую, и эти базы данных не могут быть объявлены в файле config.yml
Показать ещё 1 комментарий
Теги:

3 ответа

47

Если вы используете ConnectionFactory, ваши подписчики событий, подключенные к соединению, перестанут работать, например stofDoctrineExtensions.

Вот мой метод. У меня с ConnectionFactory есть пустое соединение и EntityManager. Во время работы я просто заменяю конфигурацию соединений с помощью Reflections. Работает на 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)
  }
}

UPDATE

Более элегантное решение для доктрины 2.2/sf 2.3 (без отклонений), созданное для php5.4 (мне нравится новый инициализатор массива: D) Мы можем использовать функцию доктрины, называемую оболочкой связи, см. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/portability.html

В этом примере используется служба сеанса для временного хранения сведений о подключении.

Сначала мы должны создать специальную оболочку связи:

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

Затем зарегистрируйте его в своей конфигурации доктрины:

…

connections:
  dynamic:
    driver:   %database_driver%
    host:     %database_host%
    port:     %database_port%
    dbname:   'empty_database'
    charset:  UTF8
    wrapper_class: 'w3des\DoctrineBundle\Connection\ConnectionWrapper'

И наш ConnectionWrapper правильно зарегистрирован. Теперь сеансовая инъекция.

Сначала создайте специальный класс CompilerPass:

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

И мы записываем наш новый класс компилятора в класс * Bundle:

public function build(ContainerBuilder $container)
{
    parent::build($container);
    $container->addCompilerPass(new ConnectionCompilerPass());
}

И что все это!

Соединение будет создано по запросу, основываясь на свойствах сеанса.

Чтобы переключить базу данных, просто используйте:

$this->get('doctrine.dbal.dynamic_connection')->forceSwitch($dbname, $dbuser, $dbpass);

<сильные > Преимущества

  • Больше размышлений
  • Создание по требованию
  • Элегантный и мощный

Недостатки

  • Вы должны вручную очистить свой менеджер сущностей или создать специальное доктрину для этого
  • Значительно больше кода
  • 1
    Какова цель dynamic_conn или dynamic_manager? Можете ли вы подробнее рассказать о том, какова их цель? Спасибо
  • 0
    У меня есть приложение, где каждый клиент имеет собственную базу данных с той же схемой, что и другие клиенты. Этот метод позволяет мне переключиться на реальную базу данных, например. привилегии сеанса
Показать ещё 6 комментариев
22

Вы можете посмотреть Symfony\Bundle\DoctrineBundle\ConnectionFactory, используя службу контейнера 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',
));

Это просто быстрый пример, но он должен начать работу.

  • 0
    Спасибо, это помогло мне! Мне было интересно, если есть способ изменить значение параметра, который определен в одном из моего файла конфигурации? Например: # app / config / config.yml параметры: my_mailer.class: Acme \ HelloBundle \ Mailer my_mailer.transport: службы sendmail: my_mailer: class:% my_mailer.class% аргументы: [% my_mailer.transport%] Могу ли я изменить значение:% my_mailer.transport%
  • 0
    Изменить из кода или из файла конфигурации?
Показать ещё 4 комментария
0

Я столкнулся с тем, что мне нужно иметь разные базы данных с одинаковой схемой для каждого клиента. Начиная с symfony 2.3, после устаревания метода resetEntityManager, я заметил, что код работает хорошо, не закрывая соединение и не перезагружая (старый Entity) Manager.

это мой текущий рабочий код:

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);
}
  • 0
    Я создаю систему инструментальных панелей, которая будет интегрировать различные базы данных в одну, предоставляя интерфейс для управления этими базами данных с помощью Symfony3.0. Дело в том, что доктрина использует файл parameters.yml для объявления соединений, и я не видел, чтобы кто-нибудь говорил о том, как добиться того же, что содержится в этом потоке с этой точки зрения. Документы также не дают понять, так как я попробовал их, я продолжаю сталкиваться с ошибками. Может кто-нибудь предоставить решение для Symfony3.0, пожалуйста .... Спасибо.
  • 0
    Вы просто скопируете код из этого поста stackoverflow.com/a/9291896/6345477

Ещё вопросы

Сообщество Overcoder
Наверх
Меню