2013-02-27 19 views
6

私はコア設定データベースを持っています、各行はいくつかの基本設定などで 'アプリケーション'です。
あなたがあなたのアプリを選択したら、その行(ID)のプロパティを使用して、ホストは行に基づいて変更することもできます。Symfony2、動的DB接続/ Doctrineサービスの早期オーバーライド

私が望むのは、必要なサイトの場所(URIに基づいて知っている)にいる場合、これらの詳細を使用してDoctrineサービスを設定するサービスを登録することです。

私はエンティティマネージャを使用して、さまざまな教義リスナー/イベント潜水艦い

私は、ConnectionFactoryと周りにプレイしましたが、これは加入者との問題を引き起こすように見えます。

Doctrineサービスを透過的に変更して、コントローラがどのDBホストとDB名に接続しているかについての知識がなくても動作できるようにするには、何が最善の方法でしょうか?

このタイプの各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

+0

$ this-> request->属性はどこから来ますか? –

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

10

これらの2つの投稿は、私自身の非常によく似た問題を解決するのに役立ちました。ここに私の解決策は、多分それは他の誰かのために有用である、次のとおりです。

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 } 

と私は取得するには、このルーティング設定を持っている:

<?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を設定しましたsymfonyの4では

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

コンソールコマンドを使用する場合、これは機能しません。 – drakonli

+0

私の例では、アクセスするデータベースを決定するためにサブドメインを使用しているので、正しいです。コンソールコマンドには、この情報が明示的に与えられ、独自のスイッチを実行する必要があります。 – Tom

1

を、あなたはラッパークラスでそれをやってのけることができます:URLの一部ですが、あなたはおそらく、あなたのセットアップに応じて他の方法でそれを得ることができます私の場合_siteパラメータ、

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

    } 
} 
関連する問題