У меня есть база данных конфигурации ядра, каждая строка – это «приложение» с некоторой базовой конфигурацией и т. Д.
После того, как вы выбрали свое приложение, я хочу подключиться к базе данных, используя свойство этой строки (ID), и хост может также измениться в зависимости от строки.
Я хочу зарегистрировать службу, которая устанавливает службу Doctrine, используя эти данные, если вы находитесь в месте на нужном сайте (что я знаю на основе URI).
Я использую диспетчер Entity и различные подписи Doctrine Listeners / Event
Я играл с ConnectionFactory, но это, похоже, вызывает проблемы с подписчиками.
Каков наилучший способ подключить что-то, что будет прозрачно модифицировать службу Doctrine, чтобы контроллеры могли действовать без какого-либо знания того, к какому узлу БД и имени БД они подключаются?
Каждая БД этого типа будет иметь одинаковую структуру, поэтому все привязки Entity правильны.
Я ищу действительно чистую реализацию, надеюсь, используя Service Container, чтобы избежать «хаков».
Кто-нибудь знает об этом?
В сочетании эти два сообщения помогли мне решить мою очень похожую проблему. Вот мое решение, возможно, это полезно для кого-то еще:
<?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() );