Skip to content

Instantly share code, notes, and snippets.

@twmobius
Created July 7, 2022 13:18
Show Gist options
  • Select an option

  • Save twmobius/7159bdb9ff008256e5c43078cbdcef10 to your computer and use it in GitHub Desktop.

Select an option

Save twmobius/7159bdb9ff008256e5c43078cbdcef10 to your computer and use it in GitHub Desktop.
Multiple Doctrine EntityManagers for Multi Tenant Applications
<?php
return [
'service_manager' => [
'delegators' => [
'doctrine.entitymanager.orm_default' => [
\App\Doctrine\MultiTenantEntityManagerDelegator::class,
],
],
],
];
<?php
namespace App\Doctrine;
use Doctrine\Common\EventManager;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query\ResultSetMapping;
use PDO;
use RuntimeException;
class MultiTenantEntityManager implements EntityManagerInterface
{
/**
* @var array|EntityManager[]
*/
private $pool = [];
/**
* @var Configuration
*/
private $ormConfiguration;
/**
* @var EventManager
*/
private $eventManager;
/**
* @param Configuration $ormConfiguration
* @param EventManager $eventManager
*/
public function __construct( Configuration $ormConfiguration, EventManager $eventManager )
{
$this->ormConfiguration = $ormConfiguration;
$this->eventManager = $eventManager;
}
private function getTenant(): ?string {
/** @todo get the tenant identifier */
}
private function loadTenantConfiguration( string $tenant ): array {
/** @todo load specific tenant configuration from a json/ configuration file whatever */
return [
'host' => '127.0.0.1',
'port' => '3306',
'user' => $tenant,
'password' => 'password',
'dbName' => $tenant,
];
}
/**
* @param string|null $tenant
*/
private function createEntityManager( ?string $tenant = null ) {
$tenant = $tenant ?? $this->getTenant();
if( $this->has($tenant) ) {
return;
}
$tenantParameters = $this->loadTenantConfiguration($tenant);
$connection = array_merge(
[
'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
'driverOptions' => [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
]
],
$tenantParameters
);
try {
$em = EntityManager::create(
$connection,
$this->ormConfiguration,
$this->eventManager
);
} catch ( ORMException $e ) {
throw new RuntimeException("Unable to create entity manager for tenant {$tenant} ({$e->getMessage()})");
}
$this->pool[$tenant] = $em;
}
/**
* @param string $tenant
*
* @return bool
*/
protected function has( string $tenant ): bool {
if( isset($this->pool[$tenant]) ) {
$current = $this->pool[$tenant];
if( $current->isOpen() ) {
return true;
}
}
return false;
}
protected function getEntityManager( ?string $tenant = null ): EntityManager {
$tenant = $tenant ?? $this->getTenant();
if( !$this->has($tenant) ) {
$this->createEntityManager($tenant);
}
return $this->pool[$tenant];
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->getEntityManager()->getConnection();
}
/**
* {@inheritdoc}
*/
public function getExpressionBuilder()
{
return $this->getEntityManager()->getExpressionBuilder();
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
$this->getEntityManager()->beginTransaction();
}
/**
* {@inheritdoc}
*/
public function transactional($func)
{
return $this->getEntityManager()->transactional($func);
}
/**
* {@inheritdoc}
*/
public function commit()
{
$this->getEntityManager()->commit();
}
/**
* {@inheritdoc}
*/
public function rollback()
{
$this->getEntityManager()->rollback();
}
/**
* {@inheritdoc}
*/
public function createQuery($dql = '')
{
return $this->getEntityManager()->createQuery($dql);
}
/**
* {@inheritdoc}
*/
public function createNamedQuery($name)
{
return $this->getEntityManager()->createNamedQuery($name);
}
/**
* {@inheritdoc}
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
return $this->getEntityManager()->createNativeQuery($sql, $rsm);
}
/**
* {@inheritdoc}
*/
public function createNamedNativeQuery($name)
{
return $this->getEntityManager()->createNamedNativeQuery($name);
}
/**
* {@inheritdoc}
*/
public function createQueryBuilder()
{
return $this->getEntityManager()->createQueryBuilder();
}
/**
* {@inheritdoc}
*/
public function getReference($entityName, $id)
{
return $this->getEntityManager()->getReference($entityName, $id);
}
/**
* {@inheritdoc}
*/
public function getPartialReference($entityName, $identifier)
{
return $this->getEntityManager()->getPartialReference($entityName, $identifier);
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->getEntityManager()->close();
}
/**
* {@inheritdoc}
*/
public function copy($entity, $deep = false)
{
return $this->getEntityManager()->copy($entity, $deep);
}
/**
* {@inheritdoc}
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
$this->getEntityManager()->lock($entity, $lockMode, $lockVersion);
}
/**
* {@inheritdoc}
*/
public function find($className, $id, $lockMode = null, $lockVersion = null)
{
return $this->getEntityManager()->find($className, $id, $lockMode, $lockVersion);
}
/**
* {@inheritdoc}
*/
public function flush($entity = null)
{
$this->getEntityManager()->flush($entity);
}
/**
* {@inheritdoc}
*/
public function getEventManager()
{
return $this->getEntityManager()->getEventManager();
}
/**
* {@inheritdoc}
*/
public function getConfiguration()
{
return $this->getEntityManager()->getConfiguration();
}
/**
* {@inheritdoc}
*/
public function isOpen()
{
return $this->getEntityManager()->isOpen();
}
/**
* {@inheritdoc}
*/
public function getUnitOfWork()
{
return $this->getEntityManager()->getUnitOfWork();
}
/**
* {@inheritdoc}
*/
public function getHydrator($hydrationMode)
{
return $this->getEntityManager()->getHydrator($hydrationMode);
}
/**
* {@inheritdoc}
*/
public function newHydrator($hydrationMode)
{
return $this->getEntityManager()->newHydrator($hydrationMode);
}
/**
* {@inheritdoc}
*/
public function getProxyFactory()
{
return $this->getEntityManager()->getProxyFactory();
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return $this->getEntityManager()->getFilters();
}
/**
* {@inheritdoc}
*/
public function isFiltersStateClean()
{
return $this->getEntityManager()->isFiltersStateClean();
}
/**
* {@inheritdoc}
*/
public function hasFilters()
{
return $this->getEntityManager()->hasFilters();
}
/**
* {@inheritdoc}
*/
public function getCache()
{
return $this->getEntityManager()->getCache();
}
/**
* {@inheritdoc}
*/
public function persist($object)
{
$this->getEntityManager()->persist($object);
}
/**
* {@inheritdoc}
*/
public function remove($object)
{
$this->getEntityManager()->remove($object);
}
/**
* {@inheritdoc}
*/
public function merge($object)
{
return $this->getEntityManager()->merge($object);
}
/**
* {@inheritdoc}
*/
public function clear($objectName = null)
{
$this->getEntityManager()->clear($objectName);
}
/**
* {@inheritdoc}
*/
public function detach($object)
{
$this->getEntityManager()->detach($object);
}
/**
* {@inheritdoc}
*/
public function refresh($object)
{
$this->getEntityManager()->refresh($object);
}
/**
* {@inheritdoc}
*/
public function getRepository($className)
{
return $this->getEntityManager()->getRepository($className);
}
/**
* {@inheritdoc}
*/
public function getClassMetadata($className)
{
return $this->getEntityManager()->getClassMetadata($className);
}
/**
* {@inheritdoc}
*/
public function getMetadataFactory()
{
return $this->getEntityManager()->getMetadataFactory();
}
/**
* {@inheritdoc}
*/
public function initializeObject($obj)
{
$this->getEntityManager()->initializeObject($obj);
}
/**
* {@inheritdoc}
*/
public function contains($object)
{
return $this->getEntityManager()->contains($object);
}
}
<?php
namespace App\Doctrine;
use DoctrineORMModule\Options\EntityManager as DoctrineORMModuleEntityManager;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\DelegatorFactoryInterface;
class MultiTenantEntityManagerDelegator implements DelegatorFactoryInterface
{
public function __invoke( ContainerInterface $container, $name, callable $callback, array $options = null)
{
$name = 'orm_default';
$key = 'entitymanager';
$options = $container->get('Configuration');
$options = $options['doctrine'];
$options = $options[$key][$name] ?? null;
if (null === $options) {
throw new \RuntimeException(
sprintf(
'Options with name "%s" could not be found in "doctrine.%s".',
$name,
$key
)
);
}
$options = new DoctrineORMModuleEntityManager($options);
$ormConfiguration = $container->get($options->getConfiguration());
$container->get($options->getEntityResolver());
// Get the default event manager and pass it down to the EntityManager
$eventManager = $container->get('doctrine.eventmanager.orm_default');
return new MultiTenantEntityManager($ormConfiguration, $eventManager);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment