2
votes

I have implemented in symfony2 a connection wrapper to connect to database depending on the subdomain. I followed the instructions on the question Symfony 2 : multiple and dynamic database connection

It works fine but when I try to update schema, symfony is selecting the name of the database (dinamic_database) I have in the configuration file instead of the name of the database (database1) I have in Session.

The problem is that the value of the private property $params in class Doctrine\DBALConnection is created in the constructor and I need to change in my override connect method but I cant change this propety because dont have any methos setParams()

config.yml

doctrine:
dbal:
    default_connection: default
    connections:
        default:
            driver:   "%database_driver%"
            host:     "%database_host%"
            port:     "%database_port%"
            dbname:   "%database_name%"
            user:     "%database_user%"
            password: "%database_password%"
            charset:  UTF8
        cliente:
            wrapper_class: 'Em\Bundle\AppBundle\Connection\ConnectionWrapper'
            driver:   "%database_driver%"
            host:     "%database_host%"
            port:     "%database_port%"
            dbname:   'dinamic_database'
            user:     "%database_user%"
            password: "%database_password%"
            charset:  UTF8
orm:
    auto_generate_proxy_classes: %kernel.debug%
    default_entity_manager: default
    entity_managers:
        default:
            connection: default
            mappings:
                EmAdminBundle: ~
                EmBackOfficeBundle: ~
                EmContabilidadBundle: ~

            filters:
                softdeleteable:
                    class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
                    enabled: true
            dql:
                string_functions:
                    MONTH: DoctrineExtensions\Query\Mysql\Month
                    YEAR: DoctrineExtensions\Query\Mysql\Year
        cliente:
            connection: cliente
            mappings:
                EmAppBundle: ~

Connection wrapper

class ConnectionWrapper extends Connection
{

const SESSION_ACTIVE_DYNAMIC_CONN = 'active_dynamic_conn';

/**
 * @var Session
 */
private $session;

/**
 * @var bool
 */
private $_isConnected = false;

/**
 * @var
 */
private $defaultDatabaseName;

/**
 * @param Session $sess
 */
public function setSession(Session $sess)
{
    $this->session = $sess;

}


/**
 * @param Session $sess
 */
public function setDefaultDatabaseName($defaultDatabaseName)
{
    $this->defaultDatabaseName = $defaultDatabaseName;
}

public function forceSwitch($dbName)
{

    if ($this->session->has(self::SESSION_ACTIVE_DYNAMIC_CONN)) {
        $current = $this->session->get(self::SESSION_ACTIVE_DYNAMIC_CONN);
        if ($current[0] === $dbName) {
            return;
        }
    }

    $this->session->set(self::SESSION_ACTIVE_DYNAMIC_CONN, [
        $dbName
    ]);

    if ($this->isConnected()) {
        $this->close();
    }
}

public function forceDefault()
{
    $this->forceSwitch($this->defaultDatabaseName);
}

/**
 * {@inheritDoc}
 */
public function connect()
{

    if (!$this->session->has(self::SESSION_ACTIVE_DYNAMIC_CONN)) {
        throw new \InvalidArgumentException('You have to inject into valid context first');
    }
    if ($this->isConnected()) {
        return true;
    }

    $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array();

    $params = $this->getParams();
    $realParams = $this->session->get(self::SESSION_ACTIVE_DYNAMIC_CONN);
    $params['dbname'] = $realParams[0];

    // $params['dbname'] is "database1"       

    $this->_conn = $this->_driver->connect($params, $params['user'], $params['password'], $driverOptions);

   $paramsAfterConection = $this->getParams();
   // $paramsAfterConection['dbname'] is "dinamic_database" 

    if ($this->_eventManager->hasListeners(Events::postConnect)) {
        $eventArgs = new ConnectionEventArgs($this);
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
    }

    $this->_isConnected = true;

    return true;
}



/**
 * {@inheritDoc}
 */
public function isConnected()
{
    return $this->_isConnected;
}

/**
 * {@inheritDoc}
 */
public function close()
{
    if ($this->isConnected()) {
        parent::close();
        $this->_isConnected = false;
    }
}
}

Database manager

class DatabaseManager
{
/**
 * @var EntityManager
 */
private $emCliente;

/**
 * @var ConnectionWrapper
 */
private $connection;

public function __construct(EntityManager $emCliente, ConnectionWrapper $connection)
{
    $this->emCliente =  $emCliente;
    $this->connection = $connection;
}

public function createDatabaseApp(AppEvent $event) {
    $app = $event->getApp();
    $database = $app->getDatabaseName();
    $this->connection->getSchemaManager()->createDatabase($database);
    $this->connection->forceSwitch($database);
    $metadatas = $this->emCliente->getMetadataFactory()->getAllMetadata();
    $tool = new SchemaTool($this->emCliente);
    $tool->createSchema($metadatas);
    $this->connection->forceDefault();

}

public function deleteDatabaseApp(AppEvent $event)
{
    $app = $event->getApp();
    $database = $app->getDatabaseName();
    if(in_array($database, $this->connection->getSchemaManager()->listDatabases())){
        $this->connection->getSchemaManager()->dropDatabase($database);
    }
}

public function updateSchema(App $app)
{
    $database = $app->getDatabaseName();
    if(in_array($database, $this->connection->getSchemaManager()->listDatabases())){
        $this->connection->forceSwitch($database);
        $metadatas = $this->emCliente->getMetadataFactory()->getAllMetadata();
        $tool = new SchemaTool($this->emCliente);

        $sm = $this->connection->getSchemaManager();

        $fromSchema = $sm->createSchema();
        //fromSchema is trying to get information from dinamic_database instead of database1

        $toSchema = $tool->getSchemaFromMetadata($metadatas);

        $comparator = new Comparator();
        $schemaDiff = $comparator->compare($fromSchema, $toSchema);
        $sqls = $schemaDiff->toSql($this->connection->getDatabasePlatform());

    }
}
public function dropSchema(App $app)
{
    $database = $app->getDatabaseName();
    if(in_array($database, $this->connection->getSchemaManager()->listDatabases())){
        $this->connection->forceSwitch($database);
        $this->connection->executeQuery('SET FOREIGN_KEY_CHECKS = 0');
        $metadatas = $this->emCliente->getMetadataFactory()->getAllMetadata();
        $tool = new SchemaTool($this->emCliente);
        $tool->dropSchema($metadatas);
        $this->connection->executeQuery('SET FOREIGN_KEY_CHECKS = 1');
        $this->connection->forceDefault();
    }
}

}
1

1 Answers

2
votes

I finally solved the problem but dont know if it is the best way

I override the constructor method in connect method in ConnectionWrapper:

public function connect()
{

    if (!$this->session->has(self::SESSION_ACTIVE_DYNAMIC_CONN)) {
        throw new \InvalidArgumentException('You have to inject into valid context first');
    }
    if ($this->isConnected()) {
        return true;
    }

    $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array();

    $params = $this->getParams();
    $realParams = $this->session->get(self::SESSION_ACTIVE_DYNAMIC_CONN);
    $params['dbname'] = $realParams[0];

    //overrride constructor in parent class Connection to set new parameter dbname
    parent::__construct($params, $this->_driver, $this->_config,$this->_eventManager);

    $this->_conn = $this->_driver->connect($params, $params['user'], $params['password'], $driverOptions);


    if ($this->_eventManager->hasListeners(Events::postConnect)) {
        $eventArgs = new ConnectionEventArgs($this);
        $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
    }

    $this->_isConnected = true;

    return true;
}