mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 16:14:03 +00:00
Initial draft
This commit is contained in:
parent
4ff82a950d
commit
5d7d208e2f
19 changed files with 376 additions and 186 deletions
11
src/Contracts/ManagesDatabaseUsers.php
Normal file
11
src/Contracts/ManagesDatabaseUsers.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Contracts;
|
||||
|
||||
use Stancl\Tenancy\DatabaseConfig;
|
||||
|
||||
interface ManagesDatabaseUsers
|
||||
{
|
||||
public function createUser(DatabaseConfig $databaseConfig): void;
|
||||
public function deleteUser(DatabaseConfig $databaseConfig): void;
|
||||
}
|
||||
|
|
@ -6,6 +6,13 @@ namespace Stancl\Tenancy\Contracts;
|
|||
|
||||
interface TenantDatabaseManager
|
||||
{
|
||||
/**
|
||||
* Return the config key that separates databases (e.g. 'database' or 'schema').
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSeparator(): string;
|
||||
|
||||
/**
|
||||
* Create a database.
|
||||
*
|
||||
|
|
|
|||
167
src/DatabaseConfig.php
Normal file
167
src/DatabaseConfig.php
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy;
|
||||
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
|
||||
|
||||
class DatabaseConfig
|
||||
{
|
||||
/** @var Tenant */
|
||||
public $tenant;
|
||||
|
||||
/** @var callable */
|
||||
public static $usernameGenerator;
|
||||
|
||||
/** @var callable */
|
||||
public static $passwordGenerator;
|
||||
|
||||
/** @var callable */
|
||||
public static $databaseNameGenerator;
|
||||
|
||||
public static function __constructStatic(): void
|
||||
{
|
||||
static::$usernameGenerator = static::$usernameGenerator ?? function (Tenant $tenant) {
|
||||
return Str::random(16);
|
||||
};
|
||||
|
||||
static::$passwordGenerator = static::$usernameGenerator ?? function (Tenant $tenant) {
|
||||
return Hash::make(Str::random(32));
|
||||
};
|
||||
|
||||
static::$databaseNameGenerator = static::$databaseNameGenerator ?? function (Tenant $tenant) {
|
||||
return config('tenancy.database.prefix') . $tenant->id . config('tenancy.database.suffix');
|
||||
};
|
||||
}
|
||||
|
||||
public function __construct(Tenant $tenant)
|
||||
{
|
||||
static::__constructStatic();
|
||||
|
||||
$this->tenant = $tenant;
|
||||
}
|
||||
|
||||
public static function generateDatabaseNameUsing(callable $databaseNameGenerator): void
|
||||
{
|
||||
static::$databaseNameGenerator = $databaseNameGenerator;
|
||||
}
|
||||
|
||||
public static function generateUsernameUsing(callable $usernameGenerator): void
|
||||
{
|
||||
static::$usernameGenerator = $usernameGenerator;
|
||||
}
|
||||
|
||||
public static function generatePasswordUsing(callable $passwordGenerator): void
|
||||
{
|
||||
static::$passwordGenerator = $passwordGenerator;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->tenant->data['_tenancy_db_name'];
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->tenant->data['_tenancy_db_username'];
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->tenant->data['_tenancy_db_password'];
|
||||
}
|
||||
|
||||
public function getTemplateDatabaseConnection(): string
|
||||
{
|
||||
return $this->tenant->data['_tenancy_db_connection'];
|
||||
}
|
||||
|
||||
public function makeCredentials(): void
|
||||
{
|
||||
$this->tenant->data['_tenancy_db_name'] = $this->getName() ?? (static::$databaseNameGenerator)($this->tenant);
|
||||
|
||||
if ($this->manager() instanceof ManagesDatabaseUsers) {
|
||||
$this->tenant->data['_tenancy_db_username'] = $this->getUsername() ?? (static::$usernameGenerator)($this->tenant);
|
||||
$this->tenant->data['_tenancy_db_password'] = $this->getPassword() ?? (static::$passwordGenerator)($this->tenant);
|
||||
}
|
||||
|
||||
$this->tenant->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Template used to construct the tenant connection. Also serves as
|
||||
* the root connection on which the tenant database is created.
|
||||
*/
|
||||
public function getTemplateConnectionName()
|
||||
{
|
||||
$name = $this->tenant->getTemplateDatabaseConnection();
|
||||
|
||||
// If we're using e.g. 'tenant', the default, template connection
|
||||
// and it doesn't exist, we'll go for the default DB template.
|
||||
if (! array_key_exists($name, config('database.connections'))) {
|
||||
$name = config('tenancy.database.template_connection') ?? DatabaseManager::$originalDefaultConnectionName;
|
||||
};
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tenant's own database connection config.
|
||||
*/
|
||||
public function connection(): array
|
||||
{
|
||||
$templateConnection = config("database.connections.{$this->getTemplateConnectionName()}");
|
||||
|
||||
return array_merge($templateConnection, $this->tenantConfig(), [
|
||||
$this->manager()->getSeparator() => $this->tenant->database()->getName(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional config for the database connection, specific to this tenant.
|
||||
*/
|
||||
public function tenantConfig(): array
|
||||
{
|
||||
$dbConfig = array_filter(array_keys($this->tenant->data), function ($key) {
|
||||
return Str::startsWith($key, '_tenancy_db_');
|
||||
});
|
||||
|
||||
// Remove DB name because we set that separately
|
||||
if (isset($dbConfig['_tenancy_db_name'])) {
|
||||
unset($dbConfig['_tenancy_db_name']);
|
||||
}
|
||||
|
||||
return array_reduce($dbConfig, function ($config, $key) {
|
||||
return array_merge($config, [
|
||||
Str::substr($key, 0, strlen('_tenancy_db_')) => $this->tenant[$key],
|
||||
]);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TenantDatabaseManager for this tenant's connection.
|
||||
*/
|
||||
public function manager(): TenantDatabaseManager
|
||||
{
|
||||
$driver = config("database.connections.{$this->getTemplateConnectionName()}.driver");
|
||||
|
||||
$databaseManagers = config('tenancy.database_managers');
|
||||
|
||||
if (! array_key_exists($driver, $databaseManagers)) {
|
||||
throw new DatabaseManagerNotRegisteredException($driver);
|
||||
}
|
||||
|
||||
/** @var TenantDatabaseManager $databaseManager */
|
||||
$databaseManager = app($databaseManagers[$driver]);
|
||||
|
||||
if ($databaseManager instanceof CanSetConnection) {
|
||||
$databaseManager->setConnection($this->getTemplateConnectionName());
|
||||
}
|
||||
|
||||
return $databaseManager;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,18 +8,20 @@ use Closure;
|
|||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
|
||||
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
|
||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseCreator;
|
||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseDeleter;
|
||||
|
||||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
*/
|
||||
class DatabaseManager
|
||||
{
|
||||
/** @var string */
|
||||
public $originalDefaultConnectionName;
|
||||
public static $originalDefaultConnectionName;
|
||||
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
|
@ -34,14 +36,11 @@ class DatabaseManager
|
|||
{
|
||||
$this->app = $app;
|
||||
$this->database = $database;
|
||||
$this->originalDefaultConnectionName = $app['config']['database.default'];
|
||||
static::$originalDefaultConnectionName = $app['config']['database.default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TenantManager instance, used to dispatch tenancy events.
|
||||
*
|
||||
* @param TenantManager $tenantManager
|
||||
* @return self
|
||||
*/
|
||||
public function withTenantManager(TenantManager $tenantManager): self
|
||||
{
|
||||
|
|
@ -52,21 +51,16 @@ class DatabaseManager
|
|||
|
||||
/**
|
||||
* Connect to a tenant's database.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return void
|
||||
*/
|
||||
public function connect(Tenant $tenant)
|
||||
{
|
||||
$this->createTenantConnection($tenant->getDatabaseName(), $tenant->getConnectionName());
|
||||
$this->createTenantConnection($tenant, $tenant->getConnectionName());
|
||||
$this->setDefaultConnection($tenant->getConnectionName());
|
||||
$this->switchConnection($tenant->getConnectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to the default non-tenant connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
|
|
@ -78,9 +72,6 @@ class DatabaseManager
|
|||
|
||||
/**
|
||||
* Change the default database connection config.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultConnection(string $connection)
|
||||
{
|
||||
|
|
@ -89,53 +80,14 @@ class DatabaseManager
|
|||
|
||||
/**
|
||||
* Create the tenant database connection.
|
||||
*
|
||||
* @param string $databaseName
|
||||
* @param string $connectionName
|
||||
* @return void
|
||||
*/
|
||||
public function createTenantConnection($databaseName, $connectionName)
|
||||
public function createTenantConnection(Tenant $tenant, $connectionName)
|
||||
{
|
||||
// Create the database connection.
|
||||
$based_on = $this->getBaseConnection($connectionName);
|
||||
$this->app['config']["database.connections.$connectionName"] = $this->app['config']['database.connections.' . $based_on];
|
||||
|
||||
// Change database name.
|
||||
$databaseName = $this->getDriver($connectionName) === 'sqlite' ? database_path($databaseName) : $databaseName;
|
||||
$separateBy = $this->separateBy($connectionName);
|
||||
|
||||
$this->app['config']["database.connections.$connectionName.$separateBy"] = $databaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the connection that $connectionName should be based on.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseConnection(string $connectionName): string
|
||||
{
|
||||
return ($connectionName !== 'tenant' ? $connectionName : null) // 'tenant' is not a specific connection, it's the default
|
||||
?? $this->app['config']['tenancy.database.based_on']
|
||||
?? $this->originalDefaultConnectionName; // tenancy.database.based_on === null => use the default connection
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the driver of a database connection.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDriver(string $connectionName): ?string
|
||||
{
|
||||
return $this->app['config']["database.connections.$connectionName.driver"];
|
||||
$this->app['config']["database.connections.$connectionName"] = $tenant->database->connection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the application's connection.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return void
|
||||
*/
|
||||
public function switchConnection(string $connection)
|
||||
{
|
||||
|
|
@ -146,16 +98,14 @@ class DatabaseManager
|
|||
|
||||
/**
|
||||
* Check if a tenant can be created.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return void
|
||||
*
|
||||
* @throws TenantCannotBeCreatedException
|
||||
* @throws DatabaseManagerNotRegisteredException
|
||||
* @throws TenantDatabaseAlreadyExistsException
|
||||
*/
|
||||
public function ensureTenantCanBeCreated(Tenant $tenant): void
|
||||
{
|
||||
if ($this->getTenantDatabaseManager($tenant)->databaseExists($database = $tenant->getDatabaseName())) {
|
||||
if ($tenant->database()->manager()->databaseExists($database = $tenant->database()->getName())) {
|
||||
throw new TenantDatabaseAlreadyExistsException($database);
|
||||
}
|
||||
}
|
||||
|
|
@ -170,50 +120,63 @@ class DatabaseManager
|
|||
*/
|
||||
public function createDatabase(Tenant $tenant, array $afterCreating = [])
|
||||
{
|
||||
$database = $tenant->getDatabaseName();
|
||||
$manager = $this->getTenantDatabaseManager($tenant);
|
||||
$tenant->database()->makeCredentials();
|
||||
|
||||
$afterCreating = array_merge(
|
||||
$afterCreating,
|
||||
$this->tenancy->event('database.creating', $database, $tenant)
|
||||
$this->tenancy->event('database.creating', $tenant->database()->getName(), $tenant)
|
||||
);
|
||||
|
||||
if ($this->app['config']['tenancy.queue_database_creation'] ?? false) {
|
||||
$chain = [];
|
||||
foreach ($afterCreating as $item) {
|
||||
if (is_string($item) && class_exists($item)) {
|
||||
$chain[] = new $item($tenant); // Classes are instantiated and given $tenant
|
||||
} elseif ($item instanceof ShouldQueue) {
|
||||
$chain[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
QueuedTenantDatabaseCreator::withChain($chain)->dispatch($manager, $database);
|
||||
$this->createDatabaseAsynchronously($tenant, $afterCreating);
|
||||
} else {
|
||||
$manager->createDatabase($database);
|
||||
foreach ($afterCreating as $item) {
|
||||
if (is_object($item) && ! $item instanceof Closure) {
|
||||
$item->handle($tenant);
|
||||
} else {
|
||||
$item($tenant);
|
||||
}
|
||||
$this->createDatabaseSynchronously($tenant, $afterCreating);
|
||||
}
|
||||
|
||||
$this->tenancy->event('database.created', $tenant->database()->getName(), $tenant);
|
||||
}
|
||||
|
||||
protected function createDatabaseAsynchronously(Tenant $tenant, array $afterCreating)
|
||||
{
|
||||
$chain = [];
|
||||
foreach ($afterCreating as $item) {
|
||||
if (is_string($item) && class_exists($item)) {
|
||||
$chain[] = new $item($tenant); // Classes are instantiated and given $tenant
|
||||
} elseif ($item instanceof ShouldQueue) {
|
||||
$chain[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$this->tenancy->event('database.created', $database, $tenant);
|
||||
QueuedTenantDatabaseCreator::withChain($chain)->dispatch($tenant->database()->manager(), $tenant->database());
|
||||
}
|
||||
|
||||
protected function createDatabaseSynchronously(Tenant $tenant, array $afterCreating)
|
||||
{
|
||||
$manager = $tenant->database()->manager();
|
||||
$manager->createDatabase($tenant->database()->getName());
|
||||
|
||||
if ($manager instanceof ManagesDatabaseUsers) {
|
||||
$manager->createUser($tenant->database());
|
||||
}
|
||||
|
||||
foreach ($afterCreating as $item) {
|
||||
if (is_object($item) && !$item instanceof Closure) {
|
||||
$item->handle($tenant);
|
||||
} else {
|
||||
$item($tenant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tenant's database.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return void
|
||||
*
|
||||
* @throws DatabaseManagerNotRegisteredException
|
||||
*/
|
||||
public function deleteDatabase(Tenant $tenant)
|
||||
{
|
||||
$database = $tenant->getDatabaseName();
|
||||
$manager = $this->getTenantDatabaseManager($tenant);
|
||||
$database = $tenant->database()->getName();
|
||||
$manager = $tenant->database()->manager();
|
||||
|
||||
$this->tenancy->event('database.deleting', $database, $tenant);
|
||||
|
||||
|
|
@ -221,50 +184,11 @@ class DatabaseManager
|
|||
QueuedTenantDatabaseDeleter::dispatch($manager, $database);
|
||||
} else {
|
||||
$manager->deleteDatabase($database);
|
||||
if ($manager instanceof ManagesDatabaseUsers) {
|
||||
$manager->deleteUser($tenant->database());
|
||||
}
|
||||
}
|
||||
|
||||
$this->tenancy->event('database.deleted', $database, $tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TenantDatabaseManager for a tenant's database connection.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return TenantDatabaseManager
|
||||
* @throws DatabaseManagerNotRegisteredException
|
||||
*/
|
||||
public function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
|
||||
{
|
||||
$driver = $this->getDriver($this->getBaseConnection($connectionName = $tenant->getConnectionName()));
|
||||
|
||||
$databaseManagers = $this->app['config']['tenancy.database_managers'];
|
||||
|
||||
if (! array_key_exists($driver, $databaseManagers)) {
|
||||
throw new DatabaseManagerNotRegisteredException($driver);
|
||||
}
|
||||
|
||||
$databaseManager = $this->app[$databaseManagers[$driver]];
|
||||
|
||||
if ($connectionName !== 'tenant' && $databaseManager instanceof CanSetConnection) {
|
||||
$databaseManager->setConnection($connectionName);
|
||||
}
|
||||
|
||||
return $databaseManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* What key on the connection config should be used to separate tenants.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @return string
|
||||
*/
|
||||
public function separateBy(string $connectionName): string
|
||||
{
|
||||
if ($this->getDriver($this->getBaseConnection($connectionName)) === 'pgsql'
|
||||
&& $this->app['config']['tenancy.database.separate_by'] === 'schema') {
|
||||
return 'schema';
|
||||
}
|
||||
|
||||
return 'database';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class QueuedTenantDatabaseCreator implements ShouldQueue
|
||||
{
|
||||
|
|
@ -18,20 +20,13 @@ class QueuedTenantDatabaseCreator implements ShouldQueue
|
|||
/** @var TenantDatabaseManager */
|
||||
protected $databaseManager;
|
||||
|
||||
/** @var string */
|
||||
protected $databaseName;
|
||||
/** @var Tenant */
|
||||
protected $tenant;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param TenantDatabaseManager $databaseManager
|
||||
* @param string $databaseName
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TenantDatabaseManager $databaseManager, string $databaseName)
|
||||
public function __construct(TenantDatabaseManager $databaseManager, Tenant $tenant)
|
||||
{
|
||||
$this->databaseManager = $databaseManager;
|
||||
$this->databaseName = $databaseName;
|
||||
$this->tenant = $tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -42,5 +37,9 @@ class QueuedTenantDatabaseCreator implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$this->databaseManager->createDatabase($this->databaseName);
|
||||
|
||||
if ($this->databaseManager instanceof ManagesDatabaseUsers) {
|
||||
$this->databaseManager->createUser($this->tenant->database());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class QueuedTenantDatabaseDeleter implements ShouldQueue
|
||||
{
|
||||
|
|
@ -18,20 +20,13 @@ class QueuedTenantDatabaseDeleter implements ShouldQueue
|
|||
/** @var TenantDatabaseManager */
|
||||
protected $databaseManager;
|
||||
|
||||
/** @var string */
|
||||
protected $databaseName;
|
||||
/** @var Tenant */
|
||||
protected $tenant;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param TenantDatabaseManager $databaseManager
|
||||
* @param string $databaseName
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TenantDatabaseManager $databaseManager, string $databaseName)
|
||||
public function __construct(TenantDatabaseManager $databaseManager, Tenant $tenant)
|
||||
{
|
||||
$this->databaseManager = $databaseManager;
|
||||
$this->databaseName = $databaseName;
|
||||
$this->tenant = $tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,6 +36,9 @@ class QueuedTenantDatabaseDeleter implements ShouldQueue
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->databaseManager->deleteDatabase($this->databaseName);
|
||||
$this->databaseManager->deleteDatabase($this->tenant->database()->getName());
|
||||
if ($this->databaseManager instanceof ManagesDatabaseUsers) {
|
||||
$this->databaseManager->deleteUser($this->tenant->database());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
|||
|
||||
public function start(Tenant $tenant)
|
||||
{
|
||||
$database = $tenant->getDatabaseName();
|
||||
if (! $this->database->getTenantDatabaseManager($tenant)->databaseExists($database)) {
|
||||
$database = $tenant->database()->getName();
|
||||
if (! $tenant->database()->manager()->databaseExists($database)) {
|
||||
throw new TenantDatabaseDoesNotExistException($database);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Stancl\Tenancy\Exceptions\TenantStorageException;
|
|||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
*/
|
||||
// todo make this class serializable
|
||||
class Tenant implements ArrayAccess
|
||||
{
|
||||
use Traits\HasArrayAccess,
|
||||
|
|
@ -91,8 +92,7 @@ class Tenant implements ArrayAccess
|
|||
}
|
||||
|
||||
/**
|
||||
* DO NOT CALL THIS METHOD FROM USERLAND. Used by storage
|
||||
* drivers to create persisted instances of Tenant.
|
||||
* Used by storage drivers to create persisted instances of Tenant.
|
||||
*
|
||||
* @param array $data
|
||||
* @return self
|
||||
|
|
@ -272,23 +272,13 @@ class Tenant implements ArrayAccess
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the tenant's database's name.
|
||||
* Get database config.
|
||||
*
|
||||
* @return string
|
||||
* @return DatabaseConfig
|
||||
*/
|
||||
public function getDatabaseName(): string
|
||||
public function database(): DatabaseConfig
|
||||
{
|
||||
return $this->data['_tenancy_db_name'] ?? ($this->config->get('tenancy.database.prefix') . $this->id . $this->config->get('tenancy.database.suffix'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tenant's database connection's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionName(): string
|
||||
{
|
||||
return $this->data['_tenancy_db_connection'] ?? 'tenant';
|
||||
return new DatabaseConfig($this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ use Illuminate\Contracts\Config\Repository;
|
|||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
|
||||
class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||
class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection, ManagesDatabaseUsers
|
||||
{
|
||||
/** @var string */
|
||||
protected $connection;
|
||||
|
|
@ -20,6 +21,11 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
|||
$this->connection = $config->get('tenancy.database_manager_connections.mysql');
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
protected function database(): Connection
|
||||
{
|
||||
return DB::connection($this->connection);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\DatabaseConfig;
|
||||
|
||||
class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager implements ManagesDatabaseUsers
|
||||
{
|
||||
public static $grants = [
|
||||
'ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROUTINE', 'CREATE TEMPORARY TABLES', 'CREATE VIEW',
|
||||
'DELETE', 'DROP', 'EVENT', 'EXECUTE', 'INDEX', 'INSERT', 'LOCK TABLES', 'REFERENCES', 'SELECT',
|
||||
'SHOW VIEW', 'TRIGGER', 'UPDATE',
|
||||
];
|
||||
|
||||
public function createUser(DatabaseConfig $databaseConfig): void
|
||||
{
|
||||
$database = $databaseConfig->getName();
|
||||
$username = $databaseConfig->getUsername();
|
||||
$hostname = $databaseConfig->connection()['host'];
|
||||
$password = $databaseConfig->getPassword();
|
||||
|
||||
$this->database()->statement("CREATE USER `{$username}`@`{$hostname}` IDENTIFIED BY `{$password}`");
|
||||
|
||||
$grants = implode(', ', static::$grants);
|
||||
|
||||
if ($this->isVersion8()) { // MySQL 8+
|
||||
$grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`$hostname`";
|
||||
} else { // MySQL 5.7
|
||||
$grantQuery = "GRANT $grants ON $database.* TO `$username`@`$hostname` IDENTIFIED BY '$password'";
|
||||
}
|
||||
|
||||
$this->database()->statement($grantQuery);
|
||||
}
|
||||
|
||||
protected function isVersion8(): bool
|
||||
{
|
||||
$version = $this->database()->select($this->db->raw('select version()'))[0]->{'version()'};
|
||||
|
||||
return version_compare($version, '8.0.0') >= 0;
|
||||
}
|
||||
|
||||
public function deleteUser(DatabaseConfig $databaseConfig): void
|
||||
{
|
||||
$this->database()->statement("DROP USER IF EXISTS '{$databaseConfig->username}'");
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,11 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
|||
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
protected function database(): Connection
|
||||
{
|
||||
return DB::connection($this->connection);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
|||
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'schema';
|
||||
}
|
||||
|
||||
protected function database(): Connection
|
||||
{
|
||||
return DB::connection($this->connection);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
|||
|
||||
class SQLiteDatabaseManager implements TenantDatabaseManager
|
||||
{
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
public function createDatabase(string $name): bool
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue