Symfony2, Dynamic DB Connection/Early override of Doctrine Service

匿名 (未验证) 提交于 2019-12-03 02:29:01

问题:

I have a Core config Database, each row is an 'App' with some basic config etc.
Once you have chosen your app, I want to connect to a database using a property of that row (ID), and the host may also change based on the row.

What I want is to register a service that sets up the Doctrine service using these details if you are in a place on the site that it's required (which I know based on URI).

I am using the Entity manager, and various Doctrine Listeners/Event subs

I've played around with the ConnectionFactory, but this appears to cause problems with the subscribers.

What is the best way to hook something up that will transparently modify the Doctrine service, so that the controllers can act without any knowledge of which DB host and DB name they are connecting to?

Each DB of this type will have the same structure so all Entity mapping is correct.

I'm looking for a really clean implementation, hopefully using the Service Container to avoid any 'hacks'.

Does anyone have any knowledge of doing this?

回答1:

Combined, these two postings helped me solve my own very similar problem. Here is my solution, maybe it is useful for someone else:

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

To get this to work, I set up services.yml as follows:

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 } 

and I have this routing configuration to get the _site parameter, which in my case is part of the URL, but you can probably get it in other ways depending on your setup:

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


回答2:

Here is the new and improved non-reflection version

#services.yml acme_app.dynamic_connection:     class: %acme.dynamic_doctrine_connection.class%     calls:         - [setDoctrineConnection, [@doctrine.dbal.default_connection]]   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;     } } 


回答3:

In symfony 4, you can pull it off with a wrapper class:

# doctrine.yaml doctrine:     dbal:       connections:         default:           wrapper_class: App\Service\Database\DynamicConnection 

The class simply extends the original Connection:

class DynamicConnection extends \Doctrine\DBAL\Connection {      public function changeDatabase(string $dbName)     {         $params = $this->getParams();          if ($this->isConnected())             $this->close();          if (isset($params['url'])) {             $params['url'] = preg_replace(                 sprintf("/(?getDatabase())),                 $dbName,                 $params['url']             );         }          if (isset($params['dbname']))             $params['dbname'] = $dbName;          parent::__construct(             $params,             $this->_driver,             $this->_config,             $this->_eventManager         );      } } 


回答4:

I took a look at your service and tried to implement it, but it looks like you were missing some arguments that needed passed into your constructor. Here is an updated version that should work:

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!