From e8c3c75d7cf19529acbdfe05e71f43f5dc6d97cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 1 Mar 2024 10:48:33 +0100 Subject: [PATCH] Simplify TenantWithDatabase interface, move tenantConfig() logic --- phpstan.neon | 4 - src/Database/Concerns/HasDatabase.php | 27 +++++- src/Database/Concerns/HasInternalKeys.php | 3 + src/Database/Contracts/TenantWithDatabase.php | 9 -- src/Database/DatabaseConfig.php | 84 +++++++++---------- 5 files changed, 68 insertions(+), 59 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 8a48a672..e333380e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -40,10 +40,6 @@ parameters: message: '#Illuminate\\Routing\\UrlGenerator#' paths: - src/Bootstrappers/FilesystemTenancyBootstrapper.php - - - message: '#Trying to invoke Closure\|null but it might not be a callable#' - paths: - - src/Database/DatabaseConfig.php - message: '#Unable to resolve the template type (TMapWithKeysKey|TMapWithKeysValue) in call to method#' paths: diff --git a/src/Database/Concerns/HasDatabase.php b/src/Database/Concerns/HasDatabase.php index 42e6555a..48d1c406 100644 --- a/src/Database/Concerns/HasDatabase.php +++ b/src/Database/Concerns/HasDatabase.php @@ -4,15 +4,38 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Concerns; +use Illuminate\Database\Eloquent\Model; use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; use Stancl\Tenancy\Database\DatabaseConfig; +/** + * @mixin Model + */ trait HasDatabase { + use HasInternalKeys; + public function database(): DatabaseConfig { - /** @var TenantWithDatabase $this */ + /** @var TenantWithDatabase&Model $this */ + $databaseConfig = []; - return new DatabaseConfig($this); + foreach ($this->getAttributes() as $key => $value) { + if (str($key)->startsWith($this->internalPrefix() . 'db_')) { + if ($key === $this->internalPrefix() . 'db_name') { + // Remove DB name because we set that separately + continue; + } + + if ($key === $this->internalPrefix() . 'db_connection') { + // Remove DB connection because that's not used here + continue; + } + + $databaseConfig[str($key)->after($this->internalPrefix() . 'db_')->toString()] = $value; + } + } + + return new DatabaseConfig($this, $databaseConfig); } } diff --git a/src/Database/Concerns/HasInternalKeys.php b/src/Database/Concerns/HasInternalKeys.php index ea70d6ed..bb7c13de 100644 --- a/src/Database/Concerns/HasInternalKeys.php +++ b/src/Database/Concerns/HasInternalKeys.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Concerns; +/** + * @mixin \Illuminate\Database\Eloquent\Model + */ trait HasInternalKeys { /** Get the internal prefix. */ diff --git a/src/Database/Contracts/TenantWithDatabase.php b/src/Database/Contracts/TenantWithDatabase.php index 76a73340..5b9bf9b8 100644 --- a/src/Database/Contracts/TenantWithDatabase.php +++ b/src/Database/Contracts/TenantWithDatabase.php @@ -11,13 +11,4 @@ interface TenantWithDatabase extends Tenant { /** Get the tenant's database config. */ public function database(): DatabaseConfig; - - /** Get the internal prefix. */ - public static function internalPrefix(): string; - - /** Get an internal key. */ - public function getInternal(string $key): mixed; - - /** Set internal key. */ - public function setInternal(string $key, mixed $value): static; } diff --git a/src/Database/DatabaseConfig.php b/src/Database/DatabaseConfig.php index f47d171e..52303606 100644 --- a/src/Database/DatabaseConfig.php +++ b/src/Database/DatabaseConfig.php @@ -18,35 +18,57 @@ class DatabaseConfig /** The tenant whose database we're dealing with. */ public Tenant&Model $tenant; - /** Database username generator (can be set by the developer.) */ - public static Closure|null $usernameGenerator = null; + /** Additional config for the database connection, specific to this tenant. */ + public array $tenantConfig; - /** Database password generator (can be set by the developer.) */ - public static Closure|null $passwordGenerator = null; + /** + * Database username generator (can be set by the developer.). + * + * @var Closure(Model&Tenant): string + */ + public static Closure $usernameGenerator; - /** Database name generator (can be set by the developer.) */ - public static Closure|null $databaseNameGenerator = null; + /** + * Database password generator (can be set by the developer.). + * + * @var Closure(Model&Tenant): string + */ + public static Closure $passwordGenerator; + + /** + * Database name generator (can be set by the developer.). + * + * @var Closure(Model&Tenant): string + */ + public static Closure $databaseNameGenerator; public static function __constructStatic(): void { - static::$usernameGenerator = static::$usernameGenerator ?? function (Model&Tenant $tenant) { - return Str::random(16); - }; + if (! isset(static::$usernameGenerator)) { + static::$usernameGenerator = function () { + return Str::random(16); + }; + } - static::$passwordGenerator = static::$passwordGenerator ?? function (Model&Tenant $tenant) { - return Hash::make(Str::random(32)); - }; + if (! isset(static::$passwordGenerator)) { + static::$passwordGenerator = function () { + return Hash::make(Str::random(32)); + }; + } - static::$databaseNameGenerator = static::$databaseNameGenerator ?? function (Model&Tenant $tenant) { - return config('tenancy.database.prefix') . $tenant->getTenantKey() . config('tenancy.database.suffix'); - }; + if (! isset(static::$databaseNameGenerator)) { + static::$databaseNameGenerator = function (Model&Tenant $tenant) { + return config('tenancy.database.prefix') . $tenant->getTenantKey() . config('tenancy.database.suffix'); + }; + } } - public function __construct(Model&Tenant $tenant) + public function __construct(Model&Tenant $tenant, array $databaseConfig) { static::__constructStatic(); $this->tenant = $tenant; + $this->tenantConfig = $databaseConfig; } public static function generateDatabaseNamesUsing(Closure $databaseNameGenerator): void @@ -134,7 +156,7 @@ class DatabaseConfig $templateConnection = $this->getTemplateConnection(); return $this->manager()->makeConnectionConfig( - array_merge($templateConnection, $this->tenantConfig()), + array_merge($templateConnection, $this->tenantConfig), $this->getName() ); } @@ -144,7 +166,7 @@ class DatabaseConfig */ public function hostConnection(): array { - $config = $this->tenantConfig(); + $config = $this->tenantConfig; $templateConnection = $this->getTemplateConnection(); if ($this->connectionDriverManager($this->getTemplateConnectionDriver()) instanceof Contracts\ManagesDatabaseUsers) { @@ -169,32 +191,6 @@ class DatabaseConfig DB::purge($this->getTenantHostConnectionName()); } - /** - * Additional config for the database connection, specific to this tenant. - */ - public function tenantConfig(): array - { - $dbConfig = array_filter(array_keys($this->tenant->getAttributes()), function ($key) { - return Str::startsWith($key, $this->tenant->internalPrefix() . 'db_'); - }); - - // Remove DB name because we set that separately - if (($pos = array_search($this->tenant->internalPrefix() . 'db_name', $dbConfig)) !== false) { - unset($dbConfig[$pos]); - } - - // Remove DB connection because that's not used inside the array - if (($pos = array_search($this->tenant->internalPrefix() . 'db_connection', $dbConfig)) !== false) { - unset($dbConfig[$pos]); - } - - return array_reduce($dbConfig, function ($config, $key) { - return array_merge($config, [ - Str::substr($key, strlen($this->tenant->internalPrefix() . 'db_')) => $this->tenant->getAttribute($key), - ]); - }, []); - } - /** Get the TenantDatabaseManager for this tenant's connection. * * @throws NoConnectionSetException|DatabaseManagerNotRegisteredException