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.
0 Comments