Skip to content

Commit

Permalink
Support multi tenants db hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
RamyHakam committed Jun 4, 2024
1 parent ca09573 commit 7e51822
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/Command/CreateDatabaseCommand.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?phpnamespace Hakam\MultiTenancyBundle\Command;use Doctrine\Persistence\ManagerRegistry;use Exception;use Hakam\MultiTenancyBundle\Enum\DatabaseStatusEnum;use Hakam\MultiTenancyBundle\Exception\MultiTenancyException;use Hakam\MultiTenancyBundle\Services\DbService;use Hakam\MultiTenancyBundle\Services\TenantDbConfigurationInterface;use Psr\EventDispatcher\EventDispatcherInterface;use Symfony\Component\Console\Application;use Symfony\Component\Console\Attribute\AsCommand;use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Exception\ExceptionInterface;use Symfony\Component\Console\Input\ArrayInput;use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;use Symfony\Component\DependencyInjection\ContainerInterface;#[AsCommand( name: 'tenant:database:create', description: 'Proxy to create a new tenant database.',)]final class CreateDatabaseCommand extends Command{ use CommandTrait; public function __construct( private Application $application, private ManagerRegistry $registry, private ContainerInterface $container, private EventDispatcherInterface $eventDispatcher, private DbService $dbService) { parent::__construct(); } protected function configure(): void { $this ->setDescription('Create and prepare new databases for a tenant') ->setAliases(['t:d:c']) ->setHelp('This command allows you to create the new database for a tenant which is added to the main database config entity'); } protected function execute(InputInterface $input, OutputInterface $output): int { try { $listOfNewDbs = $this->dbService->getListOfNotCreatedDataBases(); /** @var TenantDbConfigurationInterface $newDb */ foreach ($listOfNewDbs as $newDb) { $this->createAndPrepareDatabase($newDb, $output); $newDb->setDatabaseStatus(DatabaseStatusEnum::DATABASE_CREATED); $this->registry->getManager()->persist($newDb); } $this->registry->getManager()->flush(); $output->writeln('The new List of Databases created successfully'); return 0; } catch (Exception $e) { $output->writeln($e->getMessage()); return 1; } catch (ExceptionInterface $e) { $output->writeln($e->getMessage()); return 1; } } /** * @throws ExceptionInterface * @throws MultiTenancyException * @throws \Doctrine\DBAL\Exception */ private function createAndPrepareDatabase(TenantDbConfigurationInterface $dbConfiguration, OutputInterface $output): void { $this->dbService->createDatabase($dbConfiguration->getDbName()); $command = new MigrateCommand($this->registry, $this->container, $this->eventDispatcher); $arguments = [ 'dbId' => $dbConfiguration->getId(), ]; $greetInput = new ArrayInput($arguments); $greetInput->setInteractive(false); $command->run($greetInput, $output); }}
<?phpnamespace Hakam\MultiTenancyBundle\Command;use Doctrine\Persistence\ManagerRegistry;use Exception;use Hakam\MultiTenancyBundle\Enum\DatabaseStatusEnum;use Hakam\MultiTenancyBundle\Exception\MultiTenancyException;use Hakam\MultiTenancyBundle\Services\DbService;use Hakam\MultiTenancyBundle\Services\TenantDbConfigurationInterface;use Psr\EventDispatcher\EventDispatcherInterface;use Symfony\Component\Console\Application;use Symfony\Component\Console\Attribute\AsCommand;use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Exception\ExceptionInterface;use Symfony\Component\Console\Input\ArrayInput;use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;use Symfony\Component\DependencyInjection\ContainerInterface;#[AsCommand( name: 'tenant:database:create', description: 'Proxy to create a new tenant database.',)]final class CreateDatabaseCommand extends Command{ use CommandTrait; public function __construct( private readonly Application $application, private readonly ManagerRegistry $registry, private readonly ContainerInterface $container, private readonly EventDispatcherInterface $eventDispatcher, private readonly DbService $dbService) { parent::__construct(); } protected function configure(): void { $this ->setDescription('Create and prepare new databases for a tenant') ->setAliases(['t:d:c']) ->setHelp('This command allows you to create the new database for a tenant which is added to the main database config entity'); } protected function execute(InputInterface $input, OutputInterface $output): int { try { $listOfNewDbs = $this->dbService->getListOfNotCreatedDataBases(); /** @var TenantDbConfigurationInterface $newDb */ foreach ($listOfNewDbs as $newDb) { $this->createAndPrepareDatabase($newDb, $output); $newDb->setDatabaseStatus(DatabaseStatusEnum::DATABASE_CREATED); $this->registry->getManager()->persist($newDb); } $this->registry->getManager()->flush(); $output->writeln('The new List of Databases created successfully'); return 0; } catch (Exception $e) { $output->writeln($e->getMessage()); return 1; } catch (ExceptionInterface $e) { $output->writeln($e->getMessage()); return 1; } } /** * @throws ExceptionInterface * @throws MultiTenancyException * @throws \Doctrine\DBAL\Exception */ private function createAndPrepareDatabase(TenantDbConfigurationInterface $dbConfiguration, OutputInterface $output): void { $this->dbService->createDatabase($dbConfiguration); $command = new MigrateCommand($this->registry, $this->container, $this->eventDispatcher); $arguments = [ 'dbId' => $dbConfiguration->getId(), ]; $greetInput = new ArrayInput($arguments); $greetInput->setInteractive(false); $command->run($greetInput, $output); }}
Expand Down
53 changes: 18 additions & 35 deletions src/Services/DbService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

namespace Hakam\MultiTenancyBundle\Services;

use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;

use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Tools\DsnParser;
Expand All @@ -23,55 +22,39 @@
class DbService
{
public function __construct(
private EventDispatcherInterface $eventDispatcher,
private TenantEntityManager $tenantEntityManager,
private EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly TenantEntityManager $tenantEntityManager,
private readonly EntityManagerInterface $entityManager,
#[Autowire('%hakam.tenant_db_list_entity%')]
private string $tenantDbListEntity,
private readonly string $tenantDbListEntity,
#[Autowire('%hakam.tenant_db_credentials%')]
private array $dbCredentials
private array $dbCredentials
)
{
}

/**
* Creates a new database with the given name.
*
* @param string $dbName The name of the new database.
* @throws MultiTenancyException|Exception If the database already exists or cannot be created.
* @param TenantDbConfigurationInterface $dbConfiguration
* @return int
* @throws Exception If the database already exists or cannot be created.
* @throws MultiTenancyException If the database already exists or cannot be created.
*/
public function createDatabase(string $dbName): int
public function createDatabase(TenantDbConfigurationInterface $dbConfiguration): int
{

$dsnParser = new DsnParser(['mysql' => 'pdo_mysql']);
$tmpConnection = DriverManager::getConnection($dsnParser->parse($this->dbCredentials['db_url']));

$platform = $tmpConnection->getDatabasePlatform();
if ($tmpConnection->getDriver() instanceof AbstractMySQLDriver || $tmpConnection->getDriver() instanceof AbstractPostgreSQLDriver) {
$sql = $platform->getListDatabasesSQL();
} else {
// support SQLite
$sql = 'SELECT name FROM sqlite_master WHERE type = "database"';
}
$statement = $tmpConnection->executeQuery($sql);
$databaseList = $statement->fetchFirstColumn();

$shouldNotCreateDatabase = in_array($dbName, $databaseList);

if ($shouldNotCreateDatabase) {
throw new MultiTenancyException(sprintf('Database %s already exists.', $dbName), Response::HTTP_BAD_REQUEST);
}

$tenantConnection = DriverManager::getConnection($dsnParser->parse($dbConfiguration->getDsnUrl()));
try {
$schemaManager = method_exists($tmpConnection, 'createSchemaManager')
? $tmpConnection->createSchemaManager()
: $tmpConnection->getSchemaManager();
$schemaManager->createDatabase($dbName);
$tmpConnection->close();
$schemaManager = method_exists($tenantConnection, 'createSchemaManager')
? $tenantConnection->createSchemaManager()
: $tenantConnection->getSchemaManager();
$schemaManager->createDatabase($dbConfiguration->getDbName());
$tenantConnection->close();
return 1;

} catch (\Exception $e) {
throw new MultiTenancyException(sprintf('Unable to create new tenant database %s: %s', $dbName, $e->getMessage()), $e->getCode(), $e);
throw new MultiTenancyException(sprintf('Unable to create new tenant database %s: %s', $dbConfiguration->getDbName(), $e->getMessage()), $e->getCode(), $e);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Services/TenantDbConfigurationInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ public function getDatabaseStatus(): DatabaseStatusEnum;
* Tenant database status.
*/
public function setDatabaseStatus(DatabaseStatusEnum $databaseStatus): self;

public function getDsnUrl(): string;
}
9 changes: 9 additions & 0 deletions src/Traits/TenantDbConfigTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,13 @@ public function getDbPort(): ?string
return $this->dbPort;
}

public function getDsnUrl(): string
{
$dbHost = $this->getDbHost() ?: '127.0.0.1';
$dbPort = $this->getDbPort() ?: '3306';
$dbUsername = $this->getDbUsername();
$dbPassword = $this->getDbPassword() ? ':' . $this->getDbPassword() : '';

return sprintf('mysql://%s%s@%s:%s', $dbUsername, $dbPassword, $dbHost, $dbPort);
}
}
9 changes: 9 additions & 0 deletions tests/Unit/EventListener/DbSwitchEventListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,13 @@ public function setDbHost($dbHost)

return $this;
}

public function getDsnUrl(): string
{
$dbHost = $this->getDbHost() ?: '127.0.0.1';
$dbPort = $this->getDbPort() ?: '3306';
$dbUsername = $this->getDbUsername();
$dbPassword = $this->getDbPassword() ? ':' . $this->getDbPassword() : '';
return sprintf('mysql://%s%s@%s:%s', $dbUsername, $dbPassword, $dbHost, $dbPort);
}
}

0 comments on commit 7e51822

Please sign in to comment.