From 087733d5dbd3ea3c53c38bb927905fb9e9c6be8e Mon Sep 17 00:00:00 2001 From: Abrar Ahmad Date: Wed, 1 Feb 2023 11:02:03 +0500 Subject: [PATCH] Allow defining the tenant connection template using array syntax in config (#1040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `template_tenant_connection` can be array or string * Update TenantDatabaseManagerTest.php * Update TenantDatabaseManagerTest.php * Update TenantDatabaseManagerTest.php * Update DatabaseConfig.php * partial database config for template * Update tests/TenantDatabaseManagerTest.php Co-authored-by: lukinovec * update test name * improve test names * add comments --------- Co-authored-by: lukinovec Co-authored-by: Samuel Ć tancl --- src/Database/DatabaseConfig.php | 42 ++++++++++------ tests/TenantDatabaseManagerTest.php | 75 +++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 14 deletions(-) diff --git a/src/Database/DatabaseConfig.php b/src/Database/DatabaseConfig.php index 309d828f..52cb464c 100644 --- a/src/Database/DatabaseConfig.php +++ b/src/Database/DatabaseConfig.php @@ -87,7 +87,7 @@ class DatabaseConfig { $this->tenant->setInternal('db_name', $this->getName()); - if ($this->connectionDriverManager($this->getTemplateConnectionName()) instanceof Contracts\ManagesDatabaseUsers) { + if ($this->connectionDriverManager($this->getTemplateConnectionDriver()) instanceof Contracts\ManagesDatabaseUsers) { $this->tenant->setInternal('db_username', $this->getUsername() ?? (static::$usernameGenerator)($this->tenant)); $this->tenant->setInternal('db_password', $this->getPassword() ?? (static::$passwordGenerator)($this->tenant)); } @@ -97,11 +97,29 @@ class DatabaseConfig } } - public function getTemplateConnectionName(): string + public function getTemplateConnectionDriver(): string { - return $this->tenant->getInternal('db_connection') - ?? config('tenancy.database.template_tenant_connection') - ?? config('tenancy.database.central_connection'); + return $this->getTemplateConnection()['driver']; + } + + public function getTemplateConnection(): array + { + if ($template = $this->tenant->getInternal('db_connection')) { + return config("database.connections.{$template}"); + } + + if ($template = config('tenancy.database.template_tenant_connection')) { + return is_array($template) ? array_merge($this->getCentralConnection(), $template) : config("database.connections.{$template}"); + } + + return $this->getCentralConnection(); + } + + protected function getCentralConnection(): array + { + $centralConnectionName = config('tenancy.database.central_connection'); + + return config("database.connections.{$centralConnectionName}"); } public function getTenantHostConnectionName(): string @@ -114,8 +132,7 @@ class DatabaseConfig */ public function connection(): array { - $template = $this->getTemplateConnectionName(); - $templateConnection = config("database.connections.{$template}"); + $templateConnection = $this->getTemplateConnection(); return $this->manager()->makeConnectionConfig( array_merge($templateConnection, $this->tenantConfig()), @@ -129,10 +146,9 @@ class DatabaseConfig public function hostConnection(): array { $config = $this->tenantConfig(); - $template = $this->getTemplateConnectionName(); - $templateConnection = config("database.connections.{$template}"); + $templateConnection = $this->getTemplateConnection(); - if ($this->connectionDriverManager($template) instanceof Contracts\ManagesDatabaseUsers) { + if ($this->connectionDriverManager($this->getTemplateConnectionDriver()) instanceof Contracts\ManagesDatabaseUsers) { // We're removing the username and password because user with these credentials is not created yet // If you need to provide username and password when using PermissionControlledMySQLDatabaseManager, // consider creating a new connection and use it as `tenancy_db_connection` tenant config key @@ -196,7 +212,7 @@ class DatabaseConfig $tenantHostConnectionName = $this->getTenantHostConnectionName(); config(["database.connections.{$tenantHostConnectionName}" => $this->hostConnection()]); - $manager = $this->connectionDriverManager($tenantHostConnectionName); + $manager = $this->connectionDriverManager(config("database.connections.{$tenantHostConnectionName}.driver")); if ($manager instanceof Contracts\StatefulTenantDatabaseManager) { $manager->setConnection($tenantHostConnectionName); @@ -211,10 +227,8 @@ class DatabaseConfig * * @throws DatabaseManagerNotRegisteredException */ - protected function connectionDriverManager(string $connectionName): Contracts\TenantDatabaseManager + protected function connectionDriverManager(string $driver): Contracts\TenantDatabaseManager { - $driver = config("database.connections.{$connectionName}.driver"); - $databaseManagers = config('tenancy.database.managers'); if (! array_key_exists($driver, $databaseManagers)) { diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php index 19b74e21..5d9a15d6 100644 --- a/tests/TenantDatabaseManagerTest.php +++ b/tests/TenantDatabaseManagerTest.php @@ -390,6 +390,81 @@ test('path used by sqlite manager can be customized', function () { expect(file_exists($customPath . '/' . $name))->toBeTrue(); }); +test('the tenant connection template can be specified either by name or as a connection array', function () { + Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) { + return $event->tenant; + })->toListener()); + + config([ + 'tenancy.database.managers.mysql' => MySQLDatabaseManager::class, + 'tenancy.database.template_tenant_connection' => 'mysql', + ]); + + $name = 'foo' . Str::random(8); + $tenant = Tenant::create([ + 'tenancy_db_name' => $name, + ]); + + /** @var MySQLDatabaseManager $manager */ + $manager = $tenant->database()->manager(); + expect($manager->databaseExists($name))->toBeTrue(); + expect($manager->database()->getConfig('host'))->toBe('mysql'); + + config([ + 'tenancy.database.template_tenant_connection' => [ + 'driver' => 'mysql', + 'url' => null, + 'host' => 'mysql2', + 'port' => '3306', + 'database' => 'main', + 'username' => 'root', + 'password' => 'password', + 'unix_socket' => '', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => [], + ], + ]); + + $tenant = Tenant::create([ + 'tenancy_db_name' => $name, + ]); + + /** @var MySQLDatabaseManager $manager */ + $manager = $tenant->database()->manager(); + expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works + expect($manager->database()->getConfig('host'))->toBe('mysql2'); +}); + +test('partial tenant connection templates get merged into the central connection template', function () { + Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) { + return $event->tenant; + })->toListener()); + + config([ + 'database.connections.central.url' => 'example.com', + 'tenancy.database.template_tenant_connection' => [ + 'url' => null, + 'host' => 'mysql2', + ], + ]); + + $name = 'foo' . Str::random(8); + $tenant = Tenant::create([ + 'tenancy_db_name' => $name, + ]); + + /** @var MySQLDatabaseManager $manager */ + $manager = $tenant->database()->manager(); + expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works + expect($manager->database()->getConfig('host'))->toBe('mysql2'); + expect($manager->database()->getConfig('url'))->toBeNull(); +}); + // Datasets dataset('database_managers', [ ['mysql', MySQLDatabaseManager::class],