Multiple Database Sharing the Same app

I have an app that will be shared by multiple customers and each customer have his own separate database.

I have not yet come to solution as how to manage them so that each time user accessed URL I know which database to use. Something like this (which I find dirty). Is there a better way?

Database config I currently have


<?php


$mapping = [

	'app1.domain.com'=>'app1',

	'app2.domain.com'=>'app2',

	'app3.domain.com'=>'app3',

];


$url = $_SERVER['HTTP_HOST'];


return [

	'components' => [

    	'db' => [

        	'class' => 'yii\db\Connection',

        	'dsn' => "mysql:host=localhost;dbname={$mapping[$url]}",

        	'username' => 'user_name',

        	'password' => 'password',

        	'charset' => 'utf8',

        	'enableSchemaCache' => true,

        	// Duration of schema cache.

        	'schemaCacheDuration' => 3600,

        	// Name of the cache component used to store schema information

        	'schemaCache' => 'cache',

    	] 

	],

];

Thanks!

Hi Stefano,

I’m in the same boat, however this feature in my app is being to be implemented later on.

Initially I’ve separated these links, maybe they can help you. (Some are about Yii 1 but the concept is valid)

http://www.yiiframework.com/forum/index.php/topic/61176-change-db-connection-dynamically/

http://www.yiiframework.com/wiki/603/a-multi-tenant-strategy-using-yii-and-mysql/

http://www.yiiframework.com/wiki/500/yii-saas-multi-tenant-application-with-single-database/

http://stackoverflow.com/questions/33457716/multi-tenant-saas-built-in-yii2

Other thing I was thinking this days is that maybe will be necessary to ‘multiply’ some folders like ‘runtime’. For example, when you add a permission child in RBAC, the cache is invalidated. Doesn’t make sense invalidate the cache in app2 if the change occur only in app1 (if your user can create and assign permissions). I think other cache features can be affected too. Did you solved this in your app?

Thanks for the links. I will go thorugh them.

Currently I have not thought of the problem of caching but it will definetly be my next problem to solve after this one. I believe some sort of prefixing will be best way just like we separate sessions for front and back ends in Yii Advanced app

Yeah, is what I’m using to separate my user files, the main problem maybe will be how to configure Yii to work with this prefixed folders…

You need to dig in on how currently Yii does, then add support for prefixing. create pull request so that it goes to Yii main channel.

I think the best way is to open an issue on git explaining what you want to do. You will get a good support there from guys who knows the code. Form there we can move on.

I will appreciate if you mention me with @mtangoo if you happen to create such an issue!

or just paste a link here!

Thanks for the tip. I’m not sure if I will implement this before of you because now I’m coding my app base CRUDs. If I have some idea I share with you here.

Cool. Thanks!

Hi Colleagues,

I did this in a project using Yii 1 near two years ago, and now I need to do it again in other project using Yii 2, so I was checking if someone already did it in Yii 2.

After check your question, I tried to do it in the same way I did in Yii 1 and it works ! the difference is the method you need to use to do it.

As our db connection is an extension of the class yii/db/Connection, we can redefine the property dsn of it, but the trick is that you can’t do it if the connection is already established, so, you need to close it, redefine the dsn and open it again.

In the db config use a default database as:


'db' => [

	'class' => 'yii\db\Connection',

	'dsn' => "mysql:host=localhost;dbname=app1",

	'username' => 'user_name',

	'password' => 'password',

	'charset' => 'utf8',

	'enableSchemaCache' => true,

	// Duration of schema cache.

	'schemaCacheDuration' => 3600,

	// Name of the cache component used to store schema information

	'schemaCache' => 'cache',

];

Then in your controller extend the beforeAction to redefine the dsn like this:


public function beforeAction($action) {

	$url = $_SERVER['HTTP_HOST'];

	$parts = explode('.',$url);

	$conn = \Yii::$app->get('db');

	$conn->close();

	$conn->dsn = 'mysql:host=localhost;dbname='.$parts[0];

	$conn->open();

	if (!parent::beforeAction($action)) {

		return false;

	}

	return true;

}



It works perfectly for me.

I think you will have a problem if you are going to have a lot of requests. Opening and closing, I think, is not a good idea. If you read the link provided they have a better way which is very flexible. After reading all I could I think I will go with this:

[list=1][]Create Main connection to Control Database. This will hold user details as well as control info like which database is he assigned to et al[]Override User class so that you add a field that holds control information including database name, let us call it userDb[*]In each model, I will override getDb() using user something like below. That way each user will have only his assigned data.[/list]


public static function getDb()

{

    $dsn = sprintf('mysql:host=localhost;dbname=%s', Yii::$app->user->identity->userDb);

    

    $connection = new \yii\db\Connection([

    'dsn' => $dsn,

    'username' => $username,

    'password' => $password,

    ]);

    $connection->open();

    

    return $connection;

}

The above can reside in single Base class to all models. I find this cleaner and does not interfere with controller which should not handle underlying data (models should do)

I would like to hear your opinions on this!

Which link are you refering?

I think the best way is to turn the problem on Git describe what you want to do. You will get good support from those who know the code. We can move with style

Its Yii1 but I tailored code to Yii2 and dropped some stuffs