mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 12:04:03 +00:00
POC with no polish
Signed-off-by: michael lundbøl <michael.lundboel@gmail.com>
This commit is contained in:
parent
5dc80473d3
commit
43ee1c6596
12 changed files with 226 additions and 34 deletions
|
|
@ -31,8 +31,7 @@ return [
|
|||
'database' => [
|
||||
'based_on' => null, // The connection that will be used as a base for the dynamically created tenant connection. Set to null to use the default connection.
|
||||
'prefix' => 'tenant',
|
||||
'suffix' => '',
|
||||
'separate_by' => 'database', // database or schema (only supported by pgsql)
|
||||
'suffix' => ''
|
||||
],
|
||||
'redis' => [
|
||||
'prefix_base' => 'tenant',
|
||||
|
|
|
|||
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\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
interface ManagesDatabaseUsers extends CanSetConnection
|
||||
{
|
||||
public function createDatabaseUser(string $databaseName, Tenant $tenant): void;
|
||||
}
|
||||
|
|
@ -4,15 +4,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Contracts;
|
||||
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
interface TenantDatabaseManager
|
||||
{
|
||||
/**
|
||||
* Create a database.
|
||||
*
|
||||
* @param string $name Name of the database.
|
||||
* @param string $name Name of the database.
|
||||
* @param Tenant $tenant
|
||||
* @return bool
|
||||
*/
|
||||
public function createDatabase(string $name): bool;
|
||||
public function createDatabase(string $name, Tenant $tenant): bool;
|
||||
|
||||
/**
|
||||
* Delete a database.
|
||||
|
|
@ -29,4 +32,14 @@ interface TenantDatabaseManager
|
|||
* @return bool
|
||||
*/
|
||||
public function databaseExists(string $name): bool;
|
||||
|
||||
|
||||
/**
|
||||
* Override the base connection
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @param array $baseConfiguration
|
||||
* @return array
|
||||
*/
|
||||
public function createDatabaseConnection(Tenant $tenant, array $baseConfiguration): array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
|
@ -58,7 +59,7 @@ class DatabaseManager
|
|||
*/
|
||||
public function connect(Tenant $tenant)
|
||||
{
|
||||
$this->createTenantConnection($tenant->getDatabaseName(), $tenant->getConnectionName());
|
||||
$this->createTenantConnection($tenant);
|
||||
$this->setDefaultConnection($tenant->getConnectionName());
|
||||
$this->switchConnection($tenant->getConnectionName());
|
||||
}
|
||||
|
|
@ -90,21 +91,21 @@ class DatabaseManager
|
|||
/**
|
||||
* Create the tenant database connection.
|
||||
*
|
||||
* @param string $databaseName
|
||||
* @param string $connectionName
|
||||
* @param Tenant $tenant
|
||||
* @return void
|
||||
* @throws DatabaseManagerNotRegisteredException
|
||||
*/
|
||||
public function createTenantConnection($databaseName, $connectionName)
|
||||
public function createTenantConnection(Tenant $tenant)
|
||||
{
|
||||
// Create the database connection.
|
||||
$based_on = $this->getBaseConnection($connectionName);
|
||||
$this->app['config']["database.connections.$connectionName"] = $this->app['config']['database.connections.' . $based_on];
|
||||
$configuration = $this->getTenantDatabaseManager($tenant)
|
||||
->createDatabaseConnection(
|
||||
$tenant,
|
||||
$this->getBaseConnectionConfiguration($tenant)
|
||||
);
|
||||
|
||||
// Change database name.
|
||||
$databaseName = $this->getDriver($connectionName) === 'sqlite' ? database_path($databaseName) : $databaseName;
|
||||
$separateBy = $this->separateBy($connectionName);
|
||||
$connectionName = $tenant->getConnectionName();
|
||||
|
||||
$this->app['config']["database.connections.$connectionName.$separateBy"] = $databaseName;
|
||||
$this->app['config']->set("database.connections.{$connectionName}", $configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -188,9 +189,9 @@ class DatabaseManager
|
|||
}
|
||||
}
|
||||
|
||||
QueuedTenantDatabaseCreator::withChain($chain)->dispatch($manager, $database);
|
||||
QueuedTenantDatabaseCreator::withChain($chain)->dispatch($manager, $database, $tenant);
|
||||
} else {
|
||||
$manager->createDatabase($database);
|
||||
$manager->createDatabase($database, $tenant);
|
||||
foreach ($afterCreating as $item) {
|
||||
if (is_object($item) && ! $item instanceof Closure) {
|
||||
$item->handle($tenant);
|
||||
|
|
@ -253,18 +254,15 @@ class DatabaseManager
|
|||
}
|
||||
|
||||
/**
|
||||
* What key on the connection config should be used to separate tenants.
|
||||
* Get the connection base configuration for a tenant
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @return string
|
||||
* @param Tenant $tenant
|
||||
* @return array
|
||||
*/
|
||||
public function separateBy(string $connectionName): string
|
||||
protected function getBaseConnectionConfiguration(Tenant $tenant): array
|
||||
{
|
||||
if ($this->getDriver($this->getBaseConnection($connectionName)) === 'pgsql'
|
||||
&& $this->app['config']['tenancy.database.separate_by'] === 'schema') {
|
||||
return 'schema';
|
||||
}
|
||||
$basedOn = $this->getBaseConnection($tenant->getConnectionName());
|
||||
|
||||
return 'database';
|
||||
return $this->app['config']->get("database.connections.{$basedOn}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class QueuedTenantDatabaseCreator implements ShouldQueue
|
||||
{
|
||||
|
|
@ -21,17 +22,21 @@ class QueuedTenantDatabaseCreator implements ShouldQueue
|
|||
/** @var string */
|
||||
protected $databaseName;
|
||||
|
||||
/** @var Tenant */
|
||||
public $tenant;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param TenantDatabaseManager $databaseManager
|
||||
* @param string $databaseName
|
||||
* @return void
|
||||
* @param Tenant $tenant
|
||||
*/
|
||||
public function __construct(TenantDatabaseManager $databaseManager, string $databaseName)
|
||||
public function __construct(TenantDatabaseManager $databaseManager, string $databaseName, Tenant $tenant)
|
||||
{
|
||||
$this->databaseManager = $databaseManager;
|
||||
$this->databaseName = $databaseName;
|
||||
$this->tenant = $tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,6 +46,6 @@ class QueuedTenantDatabaseCreator implements ShouldQueue
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->databaseManager->createDatabase($this->databaseName);
|
||||
$this->databaseManager->createDatabase($this->databaseName, $this->tenant);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Stancl\Tenancy\Contracts\StorageDriver;
|
|||
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||
use Stancl\Tenancy\Exceptions\NotImplementedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantStorageException;
|
||||
use Stancl\Tenancy\Traits\ProvidesDatabaseUser;
|
||||
|
||||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
|
|
@ -22,7 +23,8 @@ use Stancl\Tenancy\Exceptions\TenantStorageException;
|
|||
class Tenant implements ArrayAccess
|
||||
{
|
||||
use Traits\HasArrayAccess,
|
||||
ForwardsCalls;
|
||||
ForwardsCalls,
|
||||
ProvidesDatabaseUser;
|
||||
|
||||
/**
|
||||
* Tenant data. A "cache" of tenant storage.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Illuminate\Database\Connection;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||
{
|
||||
|
|
@ -30,7 +31,7 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
|||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function createDatabase(string $name): bool
|
||||
public function createDatabase(string $name, Tenant $tenant): bool
|
||||
{
|
||||
$charset = $this->database()->getConfig('charset');
|
||||
$collation = $this->database()->getConfig('collation');
|
||||
|
|
@ -47,4 +48,14 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
|||
{
|
||||
return (bool) $this->database()->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createDatabaseConnection(Tenant $tenant, array $baseConfiguration): array
|
||||
{
|
||||
return array_replace_recursive($baseConfiguration, [
|
||||
'database' => $tenant->getDatabaseName(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Contracts\DatabaseUserGenerator;
|
||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager implements ManagesDatabaseUsers
|
||||
{
|
||||
public function createDatabase(string $name, Tenant $tenant): bool
|
||||
{
|
||||
parent::createDatabase($name, $tenant);
|
||||
|
||||
$this->createDatabaseUser($name, $tenant);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createDatabaseConnection(Tenant $tenant, array $baseConfiguration): array
|
||||
{
|
||||
return array_replace_recursive(
|
||||
parent::createDatabaseConnection($tenant, $baseConfiguration),
|
||||
array_filter([
|
||||
'host' => $tenant->getDatabaseHost(),
|
||||
'username' => $tenant->getDatabaseUsername(),
|
||||
'password' => $tenant->getDatabasePassword(),
|
||||
'port' => $tenant->getDatabasePort(),
|
||||
'url' => $tenant->getDatabaseUrl()
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function createDatabaseUser(string $databaseName, Tenant $tenant): void
|
||||
{
|
||||
$username = $tenant->generateDatabaseUsername();
|
||||
$password = $tenant->generateDatabasePassword();
|
||||
$appHost = $tenant->getDatabaseHost() ?? $this->getBaseConfigurationFor('host');
|
||||
|
||||
$grants = implode(', ', $tenant->getDatabaseGrants());
|
||||
|
||||
$this->database()->statement(
|
||||
"CREATE USER '$username'@'$appHost' IDENTIFIED BY '$password"
|
||||
);
|
||||
|
||||
$this->database()->statement(
|
||||
"GRANT $grants ON $databaseName.* TO '$username'@'$appHost' IDENTIFIED BY '$password'"
|
||||
);
|
||||
|
||||
$tenant->withData([
|
||||
'_tenancy_db_username' => $username,
|
||||
'_tenancy_db_password' => $password,
|
||||
'_tenancy_db_host' => $appHost,
|
||||
'_tenancy_db_link' => $tenant->getDatabaseLink()
|
||||
])->save();
|
||||
}
|
||||
|
||||
private function getBaseConfigurationFor(string $key)
|
||||
{
|
||||
return $this->database()->getConfig($key);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ use Illuminate\Database\Connection;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||
{
|
||||
|
|
@ -30,7 +31,7 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
|||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function createDatabase(string $name): bool
|
||||
public function createDatabase(string $name, Tenant $tenant): bool
|
||||
{
|
||||
return $this->database()->statement("CREATE DATABASE \"$name\" WITH TEMPLATE=template0");
|
||||
}
|
||||
|
|
@ -44,4 +45,18 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
|||
{
|
||||
return (bool) $this->database()->select("SELECT datname FROM pg_database WHERE datname = '$name'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createDatabaseConnection(Tenant $tenant, array $baseConfiguration): array
|
||||
{
|
||||
if ('pgsql' !== $baseConfiguration['driver']) {
|
||||
throw new \Exception('Mismatching driver for tenant');
|
||||
}
|
||||
|
||||
return array_replace_recursive($baseConfiguration, [
|
||||
'database' => $tenant->getDatabaseName()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Illuminate\Database\Connection;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
||||
{
|
||||
|
|
@ -30,7 +31,7 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
|||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function createDatabase(string $name): bool
|
||||
public function createDatabase(string $name, Tenant $tenant): bool
|
||||
{
|
||||
return $this->database()->statement("CREATE SCHEMA \"$name\"");
|
||||
}
|
||||
|
|
@ -44,4 +45,15 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
|||
{
|
||||
return (bool) $this->database()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'");
|
||||
}
|
||||
|
||||
public function createDatabaseConnection(Tenant $tenant, array $baseConfiguration): array
|
||||
{
|
||||
if ('pgsql' !== $baseConfiguration['driver']) {
|
||||
throw new \Exception('Mismatching driver for tenant');
|
||||
}
|
||||
|
||||
return array_replace_recursive($baseConfiguration, [
|
||||
'schema' => $tenant->getDatabaseName()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class SQLiteDatabaseManager implements TenantDatabaseManager
|
||||
{
|
||||
public function createDatabase(string $name): bool
|
||||
public function createDatabase(string $name, Tenant $tenant): bool
|
||||
{
|
||||
try {
|
||||
return fclose(fopen(database_path($name), 'w'));
|
||||
|
|
@ -30,4 +31,11 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
|
|||
{
|
||||
return file_exists(database_path($name));
|
||||
}
|
||||
|
||||
public function createDatabaseConnection(Tenant $tenant, array $baseConfiguration): array
|
||||
{
|
||||
return array_replace_recursive($baseConfiguration, [
|
||||
'database' => database_path($tenant->getDatabaseName())
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
src/Traits/ProvidesDatabaseUser.php
Normal file
54
src/Traits/ProvidesDatabaseUser.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Traits;
|
||||
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait ProvidesDatabaseUser
|
||||
{
|
||||
public function getDatabaseHost(): ?string
|
||||
{
|
||||
return $this->data['_tenancy_db_host'];
|
||||
}
|
||||
|
||||
public function getDatabaseUrl(): ?string
|
||||
{
|
||||
return $this->data['_tenancy_db_url'];
|
||||
}
|
||||
|
||||
public function getDatabaseUsername(): string
|
||||
{
|
||||
return $this->data['_tenancy_db_username'];
|
||||
}
|
||||
|
||||
public function getDatabasePassword(): string
|
||||
{
|
||||
return $this->data['_tenancy_db_password'];
|
||||
}
|
||||
|
||||
public function getDatabasePort(): ?string
|
||||
{
|
||||
return $this->data['_tenancy_db_port'] ?? null;
|
||||
}
|
||||
|
||||
public function getDatabaseGrants(): array
|
||||
{
|
||||
return $this->data['_tenancy_db_grants'] ?? $this->config['tenancy.database.grants'];
|
||||
}
|
||||
|
||||
public function generateDatabaseUsername(): string
|
||||
{
|
||||
return Str::random(16);
|
||||
}
|
||||
|
||||
public function generateDatabasePassword(): string
|
||||
{
|
||||
return Hash::make(Str::random(16));
|
||||
}
|
||||
|
||||
public function getDatabaseLink(): ?string
|
||||
{
|
||||
return $this->data['_tenancy_db_link'] ?? null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue