Implementing Multi-Tenant with Symfony and Doctrine using Wrapper Class: Step-by-Step Guide
Introduction
In this article, we will explore how to implement the multi-tenant architecture using Symfony and Doctrine.
What is multi-tenant?
The term multi-tenant refers to an architecture in which an application can serve information to different users, keeping each user’s data completely isolated and accessible only to them. This architecture presents several advantages, such as lower cost by serving multiple clients with a single instance, greater ease of maintenance by consistently fixing bugs across all clients, and greater security by keeping each client’s data separated in individual databases.
How to approach the implementation
First of all, I prepare to you this GitHub repository with a basic example to see it and not just read it.
To implement the multi-tenant with Symfony and Doctrine, we will use the wrapper_class property provided by Doctrine.
This property allows us to extend the Doctrine connection and create a method to close the current connection and open
a new connection to another database. In the config/packages/doctrine.yaml file, we can configure the connection
and specify the wrapper class:
doctrine:
dbal:
default_connection: default
connections:
default:
url: '%env(resolve:DATABASE_URL)%'
wrapper_class: App\Doctrine\DynamicConnection
The DynamicConnection class will be in charge of changing the database and will look like this:
class DynamicConnection extends Doctrine\DBAL\Connection
{
public function swapDatabase(string $urlConnection): void
{
$this->closePreviousConnection();
$params = $this->prepareConnectionParameters($urlConnection);
parent::__construct($params, $this->_driver, $this->_config, $this->_eventManager);
}
...
}
In this class, we can perform any additional treatment before making the connection change, such as managing open transactions.
It’s important to note that in the swapDatabase method we pass the complete connection string and then extract
the necessary parameters.
Also, we must change the connection according to some external parameter, such as the client’s token. This can be done using a Symfony “subscriber”:
class DynamicConnectionSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly array $databases,
) {
}
public function onKernelRequest(RequestEvent $event): void
{
$connection = $event->getRequest()->query->get('conn');
/** @var DynamicConnection $databaseConnection */
$databaseConnection = $this->entityManager->getConnection();
$urlConnection = $this->databases[$connection] ?? null;
if ($urlConnection) {
$databaseConnection->swapDatabase($urlConnection);
}
}
}
In this example, we use the “subscriber” to change the connection according to a query parameter called conn.
However, the most common is to use the client’s token to determine the database to be accessed, and modify that string with the information of said client.
Conclusions
We hope this guide has helped you understand how to implement multi-tenant using Symfony and Doctrine. Remember that this architecture offers numerous advantages, but it is also important to consider other approaches and alternatives according to your project’s requirements.
If you want to delve into this topic, we recommend exploring the official Symfony and Doctrine documentation, as well as sharing your experiences and other alternatives you know.
Thanks for reading, and we hope you succeed in implementing multi-tenant in your Symfony projects!
P.S.: If you have any questions, you can contact me by sending a DM on twitter.
¿Te ha gustado este artículo?
Explora más artículos sobre desarrollo, buenas prácticas y herramientas.