I have a Symfony2 application that I want to make multi-tenant by the use of one database pr tenant (some don't consider this to be multi-tenancy, but that's not really the point).
The documentation describes how to accomplish this. However, I want to be able to create tenants dynamically, and writing the new database connection details (and entity managers) to the config.yml file directly seems messy. I would rather have a separate database which holds the tenants and their connections, and then select the proper connection/em based on an identifier (fetched, for instance, from a the subdomain of the app - clientname.app.com).
Using this approach I should be able to accomplish this, but will at the same time probably break the ability to specify the database connection and/or entity manager when running the command line commands for updating database schemas and the likes.
Provided that what I want to do make sense, is there a clever way to achieve this?
I set ours up with a static database to handle login and tenancy information and a secondary database to hold user data
app/config/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
tenantdb:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
MyCoreBundle: ~
tenantdb:
connection: tenantdb
mappings:
MyAppBundle: ~
And then, in controllers, instead of
$something = $this->getDoctrine()
->getManager()
->getRepository('MyAppBundle:Thing')
->findAll();
we did:
$something = $this->getDoctrine()
->getManager('tenantdb')
->getRepository('MyAppBundle:Thing', 'tenantdb')
->findAll();
which you can find details of here: http://symfony.com/doc/current/cookbook/doctrine/multiple_entity_managers.html
Then, based on Symfony2, Dynamic DB Connection/Early override of Doctrine Service I set up a service to switch databases based on the subdomain of the request (e.g. tenant1.example.com tenant2.example.com)
src/MyCoreBundle/Resources/config/services.yml:
services:
my.database_switcher:
class: MyCoreBundle\EventListener\DatabaseSwitcherEventListener
arguments: [@request, @doctrine.dbal.tenantdb_connection]
scope: request
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
MyCoreBundle\EventListener\DatabaseSwitcherEventListener.php
namespace MyCoreBundle\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Connection;
class DatabaseSwitcherEventListener {
private $request;
private $connection;
public function __construct(Request $request, Connection $connection) {
$this->request = $request;
$this->connection = $connection;
}
public function onKernelRequest() {
$connection = $this->connection;
if (! $connection->isConnected()) {
$params = $this->connection->getParams();
$subdomain = __GET_SUBDOMAIN__();
$oldname = preg_replace (
"/_tenant_$subdomain|_template/",
'',
$params['dbname']
);
$params['dbname'] = $oldname . ($subdomain ? "_tenant_$subdomain"
: "_template");
$connection->__construct(
$params,
$connection->getDriver(),
$connection->getConfiguration(),
$connection->getEventManager()
);
$connection->connect();
}
}
}
For convenience sake, we have an "extra" tenant database called XXX_template which system admins connect to when making global changes. The plan is that this database is copied to tenant databases on tenant create.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With