2013-02-27 51 views
6

我有一個核心配置數據庫,每行是一個'應用程序'與一些基本的配置等
一旦你選擇你的應用程序,我想連接到數據庫使用該行(ID)的屬性,並且主機也可以基於該行而改變。Symfony2動態數據庫連接/早期覆蓋的Doctrine服務

我要的是註冊,設置了使用這些細節,如果你在現場,它的需要(我知道基於URI)在地方主義服務的服務。

我使用的實體管理器,以及各種學說監聽器/事件潛艇

我用的ConnectionFactory玩耍了,但是這似乎引起了用戶的問題。

什麼是掛鉤的東西了,將透明地修改主義服務的最佳途徑,使控制器可沒有它的數據庫主機和數據庫名稱,他們連接到任何相應的行動?

這種類型的每個DB將具有相同的結構,所以所有實體映射是正確的。

我在尋找一個真正乾淨的實施,希望使用服務容器,以避免任何「黑客」。

有沒有人有這樣做的任何知識呢?

回答

9

這是新的和改進的無反射版本

#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; 
    } 
} 
+0

也收拾這一點,所以主義與默認連接的注入,因此可以去除ContainerAware延伸。 – catchamonkey 2013-02-27 12:15:55

+0

$ this-> request->屬性來自哪裏? – 2015-09-25 14:31:42

0

我看了看你的服務,並試圖實現它,但它看起來像你缺少需要傳遞到你的構造一些參數。這裏是應該工作的更新版本:

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

嘿,已經更新了我在twitter上提到的答案。新的和改進的而不使用反射。 – catchamonkey 2013-12-29 20:09:29

10

結合起來,這兩個帖子幫助我解決了我自己非常類似的問題。這裏是我的解決方案,也許是別人有用:

<?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 
+0

當您使用控制檯命令時,這不起作用。 – drakonli 2016-04-27 13:34:34

+0

這是正確的,因爲在我的示例中,我使用子域來確定要訪問哪個數據庫。控制檯命令必須明確地給出這些信息並執行自己的切換。 – Tom 2016-08-10 12:24:00

1

在symfony中4,你可以用一個包裝類把它關閉:

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

類簡單地擴展了原來的連接:

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("/(?<=\/)%s/", preg_quote($this->getDatabase())), 
       $dbName, 
       $params['url'] 
      ); 
     } 

     if (isset($params['dbname'])) 
      $params['dbname'] = $dbName; 

     parent::__construct(
      $params, 
      $this->_driver, 
      $this->_config, 
      $this->_eventManager 
     ); 

    } 
}