Я новичок в 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. Поэтому я хотел бы иметь возможность изменять базу данных на лету.
Спасибо за помощь!
Если вы используете 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);
<сильные > Преимущества
Недостатки
Вы можете посмотреть 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',
));
Это просто быстрый пример, но он должен начать работу.
Я столкнулся с тем, что мне нужно иметь разные базы данных с одинаковой схемой для каждого клиента. Начиная с 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);
}