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:
commit
b56934674a
107 changed files with 976 additions and 734 deletions
|
|
@ -10,7 +10,7 @@ trait BelongsToPrimaryModel
|
|||
{
|
||||
abstract public function getRelationshipToPrimaryModel(): string;
|
||||
|
||||
public static function bootBelongsToPrimaryModel()
|
||||
public static function bootBelongsToPrimaryModel(): void
|
||||
{
|
||||
static::addGlobalScope(new ParentModelScope);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
24
src/Database/Concerns/CreatesDatabaseUsers.php
Normal file
24
src/Database/Concerns/CreatesDatabaseUsers.php
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
19
src/Database/Contracts/ManagesDatabaseUsers.php
Normal file
19
src/Database/Contracts/ManagesDatabaseUsers.php
Normal 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;
|
||||
}
|
||||
29
src/Database/Contracts/TenantDatabaseManager.php
Normal file
29
src/Database/Contracts/TenantDatabaseManager.php
Normal 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;
|
||||
}
|
||||
13
src/Database/Contracts/TenantWithDatabase.php
Normal file
13
src/Database/Contracts/TenantWithDatabase.php
Normal 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;
|
||||
}
|
||||
161
src/Database/DatabaseConfig.php
Normal file
161
src/Database/DatabaseConfig.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
15
src/Database/Exceptions/NoConnectionSetException.php
Normal file
15
src/Database/Exceptions/NoConnectionSetException.php
Normal 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.");
|
||||
}
|
||||
}
|
||||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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'");
|
||||
}
|
||||
}
|
||||
29
src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php
Normal file
29
src/Database/TenantDatabaseManagers/MySQLDatabaseManager.php
Normal 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'");
|
||||
}
|
||||
}
|
||||
|
|
@ -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(*)'};
|
||||
}
|
||||
}
|
||||
|
|
@ -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'");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue