Symfony2, Dynamic DB Connection / Раннее переопределение службы Doctrine Service

У меня есть база данных конфигурации ядра, каждая строка – это «приложение» с некоторой базовой конфигурацией и т. Д.
После того, как вы выбрали свое приложение, я хочу подключиться к базе данных, используя свойство этой строки (ID), и хост может также измениться в зависимости от строки.

Я хочу зарегистрировать службу, которая устанавливает службу Doctrine, используя эти данные, если вы находитесь в месте на нужном сайте (что я знаю на основе URI).

Я использую диспетчер Entity и различные подписи Doctrine Listeners / Event

Я играл с ConnectionFactory, но это, похоже, вызывает проблемы с подписчиками.

Каков наилучший способ подключить что-то, что будет прозрачно модифицировать службу Doctrine, чтобы контроллеры могли действовать без какого-либо знания того, к какому узлу БД и имени БД они подключаются?

Каждая БД этого типа будет иметь одинаковую структуру, поэтому все привязки Entity правильны.

Я ищу действительно чистую реализацию, надеюсь, используя Service Container, чтобы избежать «хаков».

Кто-нибудь знает об этом?

Related of "Symfony2, Dynamic DB Connection / Раннее переопределение службы Doctrine Service"

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

<?php namespace Calitarus\CollaborationBundle\EventListener; use Symfony\Component\HttpFoundation\Request; use Doctrine\DBAL\Connection; use Exception; use Monolog\Logger; class DatabaseSwitcherEventListener { private $request; private $connection; private $logger; public function __construct(Request $request, Connection $connection, Logger $logger) { $this->request = $request; $this->connection = $connection; $this->logger = $logger; } public function onKernelRequest() { if ($this->request->attributes->has('_site')) { $site = $this->request->attributes->get('_site'); $connection = $this->connection; $params = $this->connection->getParams(); $db_name = 'br_'.$this->request->attributes->get('_site'); // TODO: validate that this site exists if ($db_name != $params['dbname']) { $this->logger->debug('switching connection from '.$params['dbname'].' to '.$db_name); $params['dbname'] = $db_name; if ($connection->isConnected()) { $connection->close(); } $connection->__construct( $params, $connection->getDriver(), $connection->getConfiguration(), $connection->getEventManager() ); try { $connection->connect(); } catch (Exception $e) { // log and handle exception } } } } } 

Чтобы это сработало, я настроил services.yml следующим образом:

 services: cc.database_switcher: class: Calitarus\CollaborationBundle\EventListener\DatabaseSwitcherEventListener arguments: [@request, @doctrine.dbal.default_connection, @logger] scope: request tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } 

и у меня есть эта конфигурация маршрутизации, чтобы получить параметр _site, который в моем случае является частью URL-адреса, но вы можете, вероятно, получить его другими способами в зависимости от вашей настройки:

 resource: "@CCollabBundle/Controller" type: annotation prefix: /{_site} defaults: _site: default по resource: "@CCollabBundle/Controller" type: annotation prefix: /{_site} defaults: _site: default 

Вот новая и улучшенная версия без отражения

 #services.yml acme_app.dynamic_connection: class: %acme.dynamic_doctrine_connection.class% calls: - [setDoctrineConnection, [@doctrine.dbal.default_connection]] <?php namespace Acme\Bundle\AppBundle; use Doctrine\DBAL\Connection; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; use Exception; class DynamicDoctrineConnection { /** * @var Connection */ private $connection; /** * Sets the DB Name prefix to use when selecting the database to connect to * * @param Connection $connection * @return SiteDbConnection $this */ public function setDoctrineConnection(Connection $connection) { $this->connection = $connection; return $this; } public function setUpAppConnection() { if ($this->request->attributes->has('appId')) { $connection = $this->connection; $params = $this->connection->getParams(); // we also check if the current connection needs to be closed based on various things // have left that part in for information here // $appId changed from that in the connection? // if ($connection->isConnected()) { // $connection->close(); // } // Set default DB connection using appId //$params['host'] = $someHost; $params['dbname'] = 'Acme_App'.$this->request->attributes->get('appId'); // Set up the parameters for the parent $connection->__construct( $params, $connection->getDriver(), $connection->getConfiguration(), $connection->getEventManager() ); try { $connection->connect(); } catch (Exception $e) { // log and handle exception } } return $this; } } 

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

 #services.yml parameters: acme_page.dynamic_doctrine_connection.class: Acme\Bundle\PageBundle\DynamicDoctrineConnection services: acme_page.dynamic_doctrine_connection: class: %acme_page.dynamic_doctrine_connection.class% arguments: [@request, @doctrine.dbal.client_connection, @doctrine] scope: request calls: - [setContainer, [@service_container]] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } //DynamicDoctrineConnection.php <?php namespace Acme\Bundle\PageBundle; use Symfony\Component\HttpFoundation\Request; use Doctrine\DBAL\Connection; use Doctrine\Bundle\DoctrineBundle\Registry; /** * Creates a Doctrine connection from attributes in the Request */ class DynamicDoctrineConnection { private $request; private $defaultConnection; private $doctrine; public function __construct(Request $request, Connection $defaultConnection, Registry $doctrine) { $this->request = $request; $this->defaultConnection = $defaultConnection; $this->doctrine = $doctrine; } public function onKernelRequest() { if ($this->request->attributes->has('appId')) { $dbName = 'Acme_App_'.$this->request->attributes->get('appId'); $this->defaultConnection->close(); $reflectionConn = new \ReflectionObject($this->defaultConnection); $reflectionParams = $reflectionConn->getProperty('_params'); $reflectionParams->setAccessible(true); $params = $reflectionParams->getValue($this->defaultConnection); $params['dbname'] = $dbName; $reflectionParams->setValue($this->defaultConnection, $params); $reflectionParams->setAccessible(false); $this->doctrine->resetEntityManager('default'); } } по #services.yml parameters: acme_page.dynamic_doctrine_connection.class: Acme\Bundle\PageBundle\DynamicDoctrineConnection services: acme_page.dynamic_doctrine_connection: class: %acme_page.dynamic_doctrine_connection.class% arguments: [@request, @doctrine.dbal.client_connection, @doctrine] scope: request calls: - [setContainer, [@service_container]] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } //DynamicDoctrineConnection.php <?php namespace Acme\Bundle\PageBundle; use Symfony\Component\HttpFoundation\Request; use Doctrine\DBAL\Connection; use Doctrine\Bundle\DoctrineBundle\Registry; /** * Creates a Doctrine connection from attributes in the Request */ class DynamicDoctrineConnection { private $request; private $defaultConnection; private $doctrine; public function __construct(Request $request, Connection $defaultConnection, Registry $doctrine) { $this->request = $request; $this->defaultConnection = $defaultConnection; $this->doctrine = $doctrine; } public function onKernelRequest() { if ($this->request->attributes->has('appId')) { $dbName = 'Acme_App_'.$this->request->attributes->get('appId'); $this->defaultConnection->close(); $reflectionConn = new \ReflectionObject($this->defaultConnection); $reflectionParams = $reflectionConn->getProperty('_params'); $reflectionParams->setAccessible(true); $params = $reflectionParams->getValue($this->defaultConnection); $params['dbname'] = $dbName; $reflectionParams->setValue($this->defaultConnection, $params); $reflectionParams->setAccessible(false); $this->doctrine->resetEntityManager('default'); } } 

Примечание для Symfony 3+: мне нужно было создать новое соединение, а не вызывать $connection->__construct() :

  $connection = new Connection( $params, $connection->getDriver(), $connection->getConfiguration(), $connection->getEventManager() );