1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-06 02:54:06 +00:00

Merge branch 'master' into stein-j-readied-tenant

This commit is contained in:
lukinovec 2022-09-28 09:48:24 +02:00
commit b56934674a
107 changed files with 976 additions and 734 deletions

View file

@ -10,7 +10,7 @@ trait BelongsToPrimaryModel
{
abstract public function getRelationshipToPrimaryModel(): string;
public static function bootBelongsToPrimaryModel()
public static function bootBelongsToPrimaryModel(): void
{
static::addGlobalScope(new ParentModelScope);
}

View file

@ -19,7 +19,7 @@ trait BelongsToTenant
return $this->belongsTo(config('tenancy.tenant_model'), BelongsToTenant::$tenantIdColumn);
}
public static function bootBelongsToTenant()
public static function bootBelongsToTenant(): void
{
static::addGlobalScope(new TenantScope);

View file

@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Database\Concerns;
trait ConvertsDomainsToLowercase
{
public static function bootConvertsDomainsToLowercase()
public static function bootConvertsDomainsToLowercase(): void
{
static::saving(function ($model) {
$model->domain = strtolower($model->domain);

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
trait CreatesDatabaseUsers
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
parent::createDatabase($tenant);
return $this->createUser($tenant->database());
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
parent::deleteDatabase($tenant);
return $this->deleteUser($tenant->database());
}
}

View file

@ -8,7 +8,7 @@ use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
trait EnsuresDomainIsNotOccupied
{
public static function bootEnsuresDomainIsNotOccupied()
public static function bootEnsuresDomainIsNotOccupied(): void
{
static::saving(function ($self) {
if ($domain = $self->newQuery()->where('domain', $self->domain)->first()) {

View file

@ -8,7 +8,7 @@ use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
trait GeneratesIds
{
public static function bootGeneratesIds()
public static function bootGeneratesIds(): void
{
static::creating(function (self $model) {
if (! $model->getKey() && $model->shouldGenerateId()) {

View file

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\DatabaseConfig;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\DatabaseConfig;
trait HasDatabase
{

View file

@ -2,6 +2,8 @@
declare(strict_types=1);
// todo not sure if this should be in Database\
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\Domain;

View file

@ -6,26 +6,20 @@ namespace Stancl\Tenancy\Database\Concerns;
trait HasInternalKeys
{
/**
* Get the internal prefix.
*/
/** Get the internal prefix. */
public static function internalPrefix(): string
{
return 'tenancy_';
}
/**
* Get an internal key.
*/
public function getInternal(string $key)
/** Get an internal key. */
public function getInternal(string $key): mixed
{
return $this->getAttribute(static::internalPrefix() . $key);
}
/**
* Set internal key.
*/
public function setInternal(string $key, $value)
/** Set internal key. */
public function setInternal(string $key, mixed $value): static
{
$this->setAttribute(static::internalPrefix() . $key, $value);

View file

@ -16,7 +16,7 @@ trait InvalidatesResolverCache
Resolvers\RequestDataTenantResolver::class,
];
public static function bootInvalidatesResolverCache()
public static function bootInvalidatesResolverCache(): void
{
static::saved(function (Tenant $tenant) {
foreach (static::$resolvers as $resolver) {

View file

@ -19,7 +19,7 @@ trait InvalidatesTenantsResolverCache
Resolvers\RequestDataTenantResolver::class,
];
public static function bootInvalidatesTenantsResolverCache()
public static function bootInvalidatesTenantsResolverCache(): void
{
static::saved(function (Model $model) {
foreach (static::$resolvers as $resolver) {

View file

@ -10,7 +10,7 @@ use Stancl\Tenancy\Events\SyncedResourceSaved;
trait ResourceSyncing
{
public static function bootResourceSyncing()
public static function bootResourceSyncing(): void
{
static::saved(function (Syncable $model) {
/** @var ResourceSyncing $model */
@ -27,7 +27,7 @@ trait ResourceSyncing
});
}
public function triggerSyncEvent()
public function triggerSyncEvent(): void
{
/** @var Syncable $this */
event(new SyncedResourceSaved($this, tenant()));

View file

@ -4,15 +4,17 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Concerns;
use Closure;
use Stancl\Tenancy\Contracts\Tenant;
trait TenantRun
{
/**
* Run a callback in this tenant's context.
* Atomic, safely reverts to previous context.
*
* This method is atomic and safely reverts to the previous context.
*/
public function run(callable $callback)
public function run(Closure $callback): mixed
{
/** @var Tenant $this */
$originalTenant = tenant();

View file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Contracts;
use Stancl\Tenancy\Database\DatabaseConfig;
interface ManagesDatabaseUsers extends TenantDatabaseManager
{
/** Create a database user. */
public function createUser(DatabaseConfig $databaseConfig): bool;
/** Delete a database user. */
public function deleteUser(DatabaseConfig $databaseConfig): bool;
/** Does a database user exist? */
public function userExists(string $username): bool;
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Contracts;
use Stancl\Tenancy\Database\Exceptions\NoConnectionSetException;
interface TenantDatabaseManager
{
/** Create a database. */
public function createDatabase(TenantWithDatabase $tenant): bool;
/** Delete a database. */
public function deleteDatabase(TenantWithDatabase $tenant): bool;
/** Does a database exist? */
public function databaseExists(string $name): bool;
/** Construct a DB connection config array. */
public function makeConnectionConfig(array $baseConfig, string $databaseName): array;
/**
* Set the DB connection that should be used by the tenant database manager.
*
* @throws NoConnectionSetException
*/
public function setConnection(string $connection): void;
}

View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Contracts;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Database\DatabaseConfig;
interface TenantWithDatabase extends Tenant
{
public function database(): DatabaseConfig;
}

View file

@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase as Tenant;
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;
/** Database password generator (can be set by the developer.) */
public static Closure|null $passwordGenerator = null;
/** Database name generator (can be set by the developer.) */
public static Closure|null $databaseNameGenerator = null;
public static function __constructStatic(): void
{
static::$usernameGenerator = static::$usernameGenerator ?? function (Tenant $tenant) {
return Str::random(16);
};
static::$passwordGenerator = static::$passwordGenerator ?? function (Tenant $tenant) {
return Hash::make(Str::random(32));
};
static::$databaseNameGenerator = static::$databaseNameGenerator ?? function (Tenant $tenant) {
return config('tenancy.database.prefix') . $tenant->getTenantKey() . config('tenancy.database.suffix');
};
}
public function __construct(Tenant $tenant)
{
static::__constructStatic();
$this->tenant = $tenant;
}
public static function generateDatabaseNamesUsing(Closure $databaseNameGenerator): void
{
static::$databaseNameGenerator = $databaseNameGenerator;
}
public static function generateUsernamesUsing(Closure $usernameGenerator): void
{
static::$usernameGenerator = $usernameGenerator;
}
public static function generatePasswordsUsing(Closure $passwordGenerator): void
{
static::$passwordGenerator = $passwordGenerator;
}
public function getName(): ?string
{
return $this->tenant->getInternal('db_name') ?? (static::$databaseNameGenerator)($this->tenant);
}
public function getUsername(): ?string
{
return $this->tenant->getInternal('db_username') ?? null;
}
public function getPassword(): ?string
{
return $this->tenant->getInternal('db_password') ?? null;
}
/**
* Generate DB name, username & password and write them to the tenant model.
*/
public function makeCredentials(): void
{
$this->tenant->setInternal('db_name', $this->getName() ?? (static::$databaseNameGenerator)($this->tenant));
if ($this->manager() 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));
}
if ($this->tenant->exists) {
$this->tenant->save();
}
}
public function getTemplateConnectionName(): string
{
return $this->tenant->getInternal('db_connection')
?? config('tenancy.database.template_tenant_connection')
?? config('tenancy.database.central_connection');
}
/**
* Tenant's own database connection config.
*/
public function connection(): array
{
$template = $this->getTemplateConnectionName();
$templateConnection = config("database.connections.{$template}");
return $this->manager()->makeConnectionConfig(
array_merge($templateConnection, $this->tenantConfig()),
$this->getName()
);
}
/**
* 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. */
public function manager(): Contracts\TenantDatabaseManager
{
$driver = config("database.connections.{$this->getTemplateConnectionName()}.driver");
$databaseManagers = config('tenancy.database.managers');
if (! array_key_exists($driver, $databaseManagers)) {
throw new Exceptions\DatabaseManagerNotRegisteredException($driver);
}
/** @var Contracts\TenantDatabaseManager $databaseManager */
$databaseManager = app($databaseManagers[$driver]);
$databaseManager->setConnection($this->getTemplateConnectionName());
return $databaseManager;
}
}

View file

@ -7,12 +7,8 @@ namespace Stancl\Tenancy\Database;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
/**
* @internal Class is subject to breaking changes in minor and patch versions.
@ -38,7 +34,7 @@ class DatabaseManager
/**
* Connect to a tenant's database.
*/
public function connectToTenant(TenantWithDatabase $tenant)
public function connectToTenant(TenantWithDatabase $tenant): void
{
$this->purgeTenantConnection();
$this->createTenantConnection($tenant);
@ -48,7 +44,7 @@ class DatabaseManager
/**
* Reconnect to the default non-tenant connection.
*/
public function reconnectToCentral()
public function reconnectToCentral(): void
{
$this->purgeTenantConnection();
$this->setDefaultConnection($this->config->get('tenancy.database.central_connection'));
@ -57,7 +53,7 @@ class DatabaseManager
/**
* Change the default database connection config.
*/
public function setDefaultConnection(string $connection)
public function setDefaultConnection(string $connection): void
{
$this->config['database.default'] = $connection;
$this->database->setDefaultConnection($connection);
@ -66,7 +62,7 @@ class DatabaseManager
/**
* Create the tenant database connection.
*/
public function createTenantConnection(TenantWithDatabase $tenant)
public function createTenantConnection(TenantWithDatabase $tenant): void
{
$this->config['database.connections.tenant'] = $tenant->database()->connection();
}
@ -74,7 +70,7 @@ class DatabaseManager
/**
* Purge the tenant database connection.
*/
public function purgeTenantConnection()
public function purgeTenantConnection(): void
{
if (array_key_exists('tenant', $this->database->getConnections())) {
$this->database->purge('tenant');
@ -95,11 +91,11 @@ class DatabaseManager
$manager = $tenant->database()->manager();
if ($manager->databaseExists($database = $tenant->database()->getName())) {
throw new TenantDatabaseAlreadyExistsException($database);
throw new Exceptions\TenantDatabaseAlreadyExistsException($database);
}
if ($manager instanceof ManagesDatabaseUsers && $manager->userExists($username = $tenant->database()->getUsername())) {
throw new TenantDatabaseUserAlreadyExistsException($username);
if ($manager instanceof Contracts\ManagesDatabaseUsers && $manager->userExists($username = $tenant->database()->getUsername())) {
throw new Exceptions\TenantDatabaseUserAlreadyExistsException($username);
}
}
}

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Exceptions;
use Exception;
class DatabaseManagerNotRegisteredException extends Exception
{
public function __construct(string $driver)
{
parent::__construct("Database manager for driver $driver is not registered.");
}
}

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Exceptions;
use Exception;
class NoConnectionSetException extends Exception
{
public function __construct(string $manager)
{
parent::__construct("No connection was set on this $manager instance.");
}
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Exceptions;
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
class TenantDatabaseAlreadyExistsException extends TenantCannotBeCreatedException
{
public function __construct(
protected string $database,
) {
parent::__construct();
}
public function reason(): string
{
return "Database {$this->database} already exists.";
}
}

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Exceptions;
use Exception;
class TenantDatabaseDoesNotExistException extends Exception
{
public function __construct(string $database)
{
parent::__construct("Database $database does not exist.");
}
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\Exceptions;
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
class TenantDatabaseUserAlreadyExistsException extends TenantCannotBeCreatedException
{
public function __construct(
protected string $user,
) {
parent::__construct();
}
public function reason(): string
{
return "Database user {$this->user} already exists.";
}
}

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Stancl\Tenancy\Contracts;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Database\Concerns;
@ -25,7 +26,7 @@ class Domain extends Model implements Contracts\Domain
protected $guarded = [];
public function tenant()
public function tenant(): BelongsTo
{
return $this->belongsTo(config('tenancy.tenant_model'));
}

View file

@ -5,17 +5,20 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Models;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Stancl\Tenancy\Database\Concerns\CentralConnection;
use Stancl\Tenancy\Exceptions\StatefulGuardRequiredException;
/**
* @param string $token
* @param string $tenant_id
* @param string $user_id
* @param string $auth_guard
* @param string $redirect_url
* @param Carbon $created_at
* @property string $token
* @property string $tenant_id
* @property string $user_id
* @property string $auth_guard
* @property string $redirect_url
* @property Carbon $created_at
*/
class ImpersonationToken extends Model
{
@ -35,14 +38,18 @@ class ImpersonationToken extends Model
'created_at',
];
public static function boot()
public static function booted(): void
{
parent::boot();
static::creating(function ($model) {
$authGuard = $model->auth_guard ?? config('auth.defaults.guard');
if (! Auth::guard($authGuard) instanceof StatefulGuard) {
throw new StatefulGuardRequiredException($authGuard);
}
$model->created_at = $model->created_at ?? $model->freshTimestamp();
$model->token = $model->token ?? Str::random(128);
$model->auth_guard = $model->auth_guard ?? config('auth.defaults.guard');
$model->auth_guard = $authGuard;
});
}
}

View file

@ -40,7 +40,7 @@ class Tenant extends Model implements Contracts\Tenant
return 'id';
}
public function getTenantKey()
public function getTenantKey(): int|string
{
return $this->getAttribute($this->getTenantKeyName());
}

View file

@ -9,10 +9,8 @@ use Stancl\Tenancy\Contracts\Syncable;
class TenantPivot extends Pivot
{
public static function boot()
public static function booted(): void
{
parent::boot();
static::saved(function (self $pivot) {
$parent = $pivot->pivotParent;

View file

@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Scope;
class ParentModelScope implements Scope
{
public function apply(Builder $builder, Model $model)
public function apply(Builder $builder, Model $model): void
{
if (! tenancy()->initialized) {
return;

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database;
use Closure;
use Illuminate\Database\Eloquent\Collection;
use Stancl\Tenancy\Contracts\Tenant;
@ -16,7 +17,7 @@ use Stancl\Tenancy\Contracts\Tenant;
*/
class TenantCollection extends Collection
{
public function runForEach(callable $callable, bool $withPending = null): self
public function runForEach(Closure $callable, bool $withPending = null): self
{
tenancy()->runForMultiple($this->items, $callable, $withPending);

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class MicrosoftSQLDatabaseManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
$database = $tenant->database()->getName();
$charset = $this->database()->getConfig('charset');
$collation = $this->database()->getConfig('collation'); // todo check why these are not used
return $this->database()->statement("CREATE DATABASE [{$database}]");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->database()->statement("DROP DATABASE [{$tenant->database()->getName()}]");
}
public function databaseExists(string $name): bool
{
return (bool) $this->database()->select("SELECT name FROM master.sys.databases WHERE name = '$name'");
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class MySQLDatabaseManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
$database = $tenant->database()->getName();
$charset = $this->database()->getConfig('charset');
$collation = $this->database()->getConfig('collation');
return $this->database()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->database()->statement("DROP DATABASE `{$tenant->database()->getName()}`");
}
public function databaseExists(string $name): bool
{
return (bool) $this->database()->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
}
}

View file

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Concerns\CreatesDatabaseUsers;
use Stancl\Tenancy\Database\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Database\DatabaseConfig;
class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager implements ManagesDatabaseUsers
{
use CreatesDatabaseUsers;
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): bool
{
$database = $databaseConfig->getName();
$username = $databaseConfig->getUsername();
$hostname = $databaseConfig->connection()['host'];
$password = $databaseConfig->getPassword();
$this->database()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");
$grants = implode(', ', static::$grants);
if ($this->isVersion8()) { // MySQL 8+
$grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`%`";
} else { // MySQL 5.7
$grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`%` IDENTIFIED BY '$password'";
}
return $this->database()->statement($grantQuery);
}
protected function isVersion8(): bool
{
$version = $this->database()->select($this->database()->raw('select version()'))[0]->{'version()'};
return version_compare($version, '8.0.0') >= 0;
}
public function deleteUser(DatabaseConfig $databaseConfig): bool
{
return $this->database()->statement("DROP USER IF EXISTS '{$databaseConfig->getUsername()}'");
}
public function userExists(string $username): bool
{
return (bool) $this->database()->select("SELECT count(*) FROM mysql.user WHERE user = '$username'")[0]->{'count(*)'};
}
}

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class PostgreSQLDatabaseManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
return $this->database()->statement("CREATE DATABASE \"{$tenant->database()->getName()}\" WITH TEMPLATE=template0");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->database()->statement("DROP DATABASE \"{$tenant->database()->getName()}\"");
}
public function databaseExists(string $name): bool
{
return (bool) $this->database()->select("SELECT datname FROM pg_database WHERE datname = '$name'");
}
}

View file

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class PostgreSQLSchemaManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
return $this->database()->statement("CREATE SCHEMA \"{$tenant->database()->getName()}\"");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->database()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\" CASCADE");
}
public function databaseExists(string $name): bool
{
return (bool) $this->database()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'");
}
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
if (version_compare(app()->version(), '9.0', '>=')) {
$baseConfig['search_path'] = $databaseName;
} else {
$baseConfig['schema'] = $databaseName;
}
return $baseConfig;
}
}

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Throwable;
class SQLiteDatabaseManager implements TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
try {
return file_put_contents(database_path($tenant->database()->getName()), '');
} catch (Throwable) {
return false;
}
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
try {
return unlink(database_path($tenant->database()->getName()));
} catch (Throwable) {
return false;
}
}
public function databaseExists(string $name): bool
{
return file_exists(database_path($name));
}
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
$baseConfig['database'] = database_path($databaseName);
return $baseConfig;
}
public function setConnection(string $connection): void
{
//
}
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Database\Contracts\TenantDatabaseManager as Contract;
use Stancl\Tenancy\Database\Exceptions\NoConnectionSetException;
abstract class TenantDatabaseManager implements Contract // todo better naming?
{
/** The database connection to the server. */
protected string $connection;
protected function database(): Connection
{
if (! isset($this->connection)) {
throw new NoConnectionSetException(static::class);
}
return DB::connection($this->connection);
}
public function setConnection(string $connection): void
{
$this->connection = $connection;
}
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
$baseConfig['database'] = $databaseName;
return $baseConfig;
}
}