2013-05-26 22 views
15

Il mio progetto symfony2 ha un database principale e molti database figlio. Ogni database figlio viene creato per ogni utente, le credenziali del database sono memorizzate nel database principale. Quando l'utente esegue l'accesso, le credenziali del database specifiche dell'utente vengono recuperate dal database principale e la connessione al database figlio dovrebbe essere stabilita idealmente. Ho cercato su google per lo stesso, e mi sono imbattuto una serie di soluzioni e, infine, ha fatto la seguente:Connessione dinamica al database symfony2

#config.yml 

doctrine: 
dbal: 
    default_connection:  default 
    connections: 
     default: 
      dbname:   maindb 
      user:    root 
      password:   null 
      host:    localhost 
     dynamic_conn: 
      dbname:   ~ 
      user:    ~ 
      password:   ~ 
      host:    localhost 
orm: 
    default_entity_manager: default 
    entity_managers: 
     default: 
      connection:  default 
      auto_mapping:  true 
     dynamic_em: 
      connection:  dynamic_conn 
      auto_mapping:  true 

ho creato una connessione predefinita per la connessione al database principale e una connessione a vuoto per il database del bambino, allo stesso modo io creato gestori di entità. Poi ho creato listener di eventi di default e ha aggiunto il seguente codice al 'onKernelRequest':

public function onKernelRequest(GetResponseEvent $event) //works like preDispatch in Zend 
{ 
    //code to get db credentials from master database and stored in varaiables 
    .... 
    $connection = $this->container->get(sprintf('doctrine.dbal.%s_connection', 'dynamic_conn')); 
    $connection->close(); 

    $refConn = new \ReflectionObject($connection); 
    $refParams = $refConn->getProperty('_params'); 
    $refParams->setAccessible('public'); //we have to change it for a moment 

    $params = $refParams->getValue($connection); 
    $params['dbname'] = $dbName; 
    $params['user'] = $dbUser; 
    $params['password'] = $dbPass; 

    $refParams->setAccessible('private'); 
    $refParams->setValue($connection, $params); 
    $this->container->get('doctrine')->resetEntityManager('dynamic_em'); 
    .... 
} 

Il codice di cui sopra imposta i parametri del database bambino e reimposta il gestore di entità dynamic_em.

Quando eseguo le operazioni seguenti in alcuni controller, funziona correttamente e i dati vengono recuperati dal database figlio.

$getblog= $em->getRepository('BloggerBlogBundle:Blog')->findById($id); //uses doctrine 

Ma, quando io uso contesto di protezione come si vede nel codice seguente, ottengo un errore 'NO DATABASE SELEZIONATO'.

$securityContext = $this->container->get('security.context'); 
$loggedinUserid = $securityContext->getToken()->getUser()->getId(); 

Come posso impostare la connessione al database in modo dinamico e utilizzare contesto di sicurezza come bene?

UPDATE: -

Dopo molto tempo speso per tentativi ed errori, e googling intorno, mi sono reso conto che security.context è impostato prima dell'esecuzione di onKernelRequest. Ora la domanda è in che modo inserire i dettagli della connessione al database in security.context e dove iniettare?

Abbiamo bisogno di arrivare a un punto in cui il contesto DBAL e sicurezza è impostato e il token di sicurezza viene creato, e possiamo manipolare i dettagli della connessione al database.

Quindi, come dichiarato dal seguente link, ho apportato delle modifiche al mio codice, questo è esattamente quello che vorrei fare. http://forum.symfony-project.org/viewtopic.php?t=37398&p=124413

che mi lascia il seguente codice aggiungi al mio progetto:

#config.yml //remains unchanged, similar to above code 

un passaggio compilatore è creato come segue:

// src/Blogger/BlogBundle/BloggerBlogBundle.php 
namespace Blogger\BlogBundle; 

use Symfony\Component\HttpKernel\Bundle\Bundle; 
use Symfony\Component\DependencyInjection\ContainerBuilder; 

use Blogger\BlogBundle\DependencyInjection\Compiler\CustomCompilerPass; 

class BloggerBlogBundle extends Bundle 
{ 
    public function build(ContainerBuilder $container) 
    { 
     parent::build($container); 

     $container->addCompilerPass(new CustomCompilerPass()); 
    } 
} 

Il pass compilatore è la seguente:

# src/Blogger/BlogBundle/DependencyInjection/Compiler/CustomCompilerPass.php 

class CustomCompilerPassimplements CompilerPassInterface 
{ 
    public function process(ContainerBuilder $container) 
    { 
     $connection_service = 'doctrine.dbal.dynamic_conn_connection'; 
     if ($container->hasDefinition($connection_service)) 
     { 
      $def = $container->getDefinition($connection_service); 
      $args = $def->getArguments(); 
      $args[0]['driverClass'] = 'Blogger\BlogBundle\UserDependentMySqlDriver'; 
      $args[0]['driverOptions'][] = array(new Reference('security.context')); 
      $def->replaceArgument(0, $args[0]); 
     } 
    } 
} 

Il codice classe driver è il seguente:

Il codice sopra riportato è stato aggiunto al mio progetto e presumo che questo sia il vero problema del mio problema.

Ma ora ottengo il seguente errore:

ServiceCircularReferenceException: Circular reference detected for service "security.context", path: "profiler_listener -> profiler -> security.context -> security.authentication.manager -> fos_user.user_provider.username_email -> fos_user.user_manager -> doctrine.orm.dynamic_manager_entity_manager -> doctrine.dbal.dynamic_conn_connection".

Come, posso ottenere il mio codice per funzionare? Scommetto che sto facendo qualcosa di sbagliato qui e apprezzerei ogni suggerimento e aiuto.

+1

è il codice utilizzando il 'security.context' corse * prima * l'evento' onKernelRequest'? alcune dipendenze di security.context devono utilizzare 'dynamic_em' – arnaud576875

+1

Il codice security.context viene aggiunto alla stessa azione in cui viene aggiunto il codice getRespository. Quindi suppongo, che onKernelRequest sia eseguito prima di security.context. –

+1

@John L'ho appena testato con Sf 2.3.2 e la tua prima soluzione funziona per me. Ho usato un sottoscrittore di eventi per modificare l'oggetto di connessione. Il controller in seguito recuperò una riga dal database "slave" e caricò l'utente attualmente connesso (usato 'FOSUserBundle'). Il contesto di sicurezza viene popolato durante la fase di dispacciamento dell'evento e, a prima vista, ho pensato che si trattasse semplicemente di cambiare le priorità degli ascoltatori. Se non l'hai già capito, forse potresti dirmi quale versione di Symfony stai usando? – gilden

risposta

3

Vorrei proporre una soluzione diversa al problema originale. È possibile utilizzare PhpFileLoader per definire dinamicamente i parametri per config.yml.

  1. estratto voi principali parametri di connessione al database in file a parte:

    # src/Blogger/BlogBundle/Resources/config/parameters.yml 
    
    parameters: 
        main_db_name:   maindb 
        main_db_user:   root 
        main_db_password:  null 
        main_db_host:   localhost 
    
  2. Crea nuovo script PHP (diciamo DynamicParametersLoader.php), che inietterà nuovi parametri in un contenitore app. Penso che tu non possa usare la tua app symfony in questo script, ma puoi leggere le credenziali db principali dalla variabile $ container. Come il seguente:

    # src/Blogger/BlogBundle/DependecyInjection/DynamicParametersLoader.php 
    <?php 
    
    $mainDbName = $container->getParameter('main_db_name'); 
    $mainDbUser = $container->getParameter('main_db_user'); 
    $mainDbPassword = $container->getParameter('main_db_password'); 
    $mainDbHost = $container->getParameter('main_db_host'); 
    
    # whatever code to query your main database for dynamic DB credentials. You cannot use your symfony2 app services here, so it ought to be plain PHP. 
    ... 
    
    # creating new parameters in container 
    $container->setParameter('dynamic_db_name', $dbName); 
    $container->setParameter('dynamic_db_user', $dbUser); 
    $container->setParameter('dynamic_db_password', $dbPass); 
    
  3. Ora è necessario dire a Symfony sulla tua sceneggiatura e nuovo file parameters.yml:

    # config.yml 
    imports: 
        - { resource: parameters.yml } 
        - { resource: ../../DependencyInjection/DynamicParametersLoader.php } 
    
  4. A questo punto è possibile utilizzare liberamente i parametri iniettati in voi config:

    # config.yml 
    ... 
         dynamic_conn: 
          dbname:   %dynamic_db_name% 
          user:    %dynamic_db_user% 
          password:   %dynamic_db_password% 
    ... 
    
+0

Questa soluzione non funzionerà perché i parametri sono già stati sostituiti nei segnaposto. Inoltre, rigenererai un nuovo contenitore ogni volta che devi cambiare. Sarà un omicidio per le prestazioni. –

+0

Alexandre, ci sono diversi parametri nei segnaposto (dynamic_db_name vs main_db_name). – Troggy

+0

my dynamic_db_name è basato su url e in DynamicParametersLoader.php l'ho ricevuto da url e usato in connessione. –

5

Qui, è necessario implementare la propria logica da soli, i n la tua attività.

Dai un'occhiata alla documentazione di Doctrine su "come creare un gestore di entità".

quindi creare un servizio con un API chiara:

$this->get('em_factory')->getManager('name-of-my-client'); // returns an EntityManager 

Non si può farlo con difetto DoctrineBundle, non è utilizzabile per le caratteristiche dinamiche.

class EmFactory 
{ 
    public function getManager($name) 
    { 
     // you can get those values: 
     // - autoguess, based on name 
     // - injection through constructor 
     // - other database connection 
     // just create constructor and inject what you need 
     $params = array('username' => $name, 'password' => $name, ....); 

     // get an EM up and running 
     // see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html#obtaining-the-entitymanager 

     return $em; 
    } 
} 

E dichiarare come servizio.

+0

Mi piace questa risposta più della mia. È molto più allineato. – Troggy

Problemi correlati