1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 23:14:03 +00:00

Tenant-specific connections, some work to get tests running

This commit is contained in:
Samuel Štancl 2019-09-15 17:44:26 +02:00
parent e25a01a997
commit c65b6839ff
13 changed files with 162 additions and 67 deletions

View file

@ -3,9 +3,9 @@
declare(strict_types=1);
return [
'storage_driver' => 'Stancl\Tenancy\StorageDrivers\DatabaseStorageDriver',
'storage_driver' => 'Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver',
'storage' => [
'db' => [ // Stancl\Tenancy\StorageDrivers\DatabaseStorageDriver
'db' => [ // Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver
'data_column' => 'data',
'custom_columns' => [
// 'plan',
@ -60,6 +60,7 @@ return [
'cache' => 'Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper',
'filesystem' => 'Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper',
'redis' => 'Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper',
'queue' => 'Stancl\Tenancy\TenancyBoostrappers\QueueTenancyBootstrapper',
],
'features' => [
// Features are classes that provide additional functionality

View file

@ -16,10 +16,8 @@ class CreateTenantsTable extends Migration
public function up()
{
Schema::create('tenants', function (Blueprint $table) {
$table->string('uuid', 36)->primary(); // don't change this
$table->string('domain', 255)->index(); // don't change this
// your indexed columns go here
$table->string('id', 36)->primary(); // 36 characters is the default uuid length
// your custom, indexed columns go here
$table->json('data');
});

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDomainsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('domains', function (Blueprint $table) {
$table->string('tenant_id', 36)->primary(); // 36 characters is the default uuid length
$table->string('domain', 255)->index(); // don't change this
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('domains');
}
}

View file

@ -48,6 +48,14 @@ interface StorageDriver
*/
public function ensureTenantCanBeCreated(Tenant $tenant): void;
/**
* Set default tenant (will be used for get/put when no tenant is supplied).
*
* @param Tenant $tenant
* @return self
*/
public function withDefaultTenant(Tenant $tenant);
/**
* Get a value from storage.
*

View file

@ -6,6 +6,7 @@ namespace Stancl\Tenancy;
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
use Illuminate\Foundation\Application;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseCreator;
@ -23,6 +24,7 @@ class DatabaseManager
public function __construct(Application $app, BaseDatabaseManager $database)
{
$this->app = $app;
$this->database = $database;
$this->originalDefaultConnectionName = $app['config']['database.default'];
}
@ -35,9 +37,8 @@ class DatabaseManager
*/
public function connect(Tenant $tenant)
{
$connection = 'tenant'; // todo tenant-specific connections
$this->createTenantConnection($tenant->getDatabaseName(), $connection);
$this->switchConnection($connection);
$this->createTenantConnection($tenant);
$this->switchConnection($tenant->getConnectionName());
}
/**
@ -53,13 +54,13 @@ class DatabaseManager
/**
* Create the tenant database connection.
*
* @param string $databaseName
* @param string $connectionName
* @param Tenant $tenant
* @return void
*/
public function createTenantConnection(string $databaseName, string $connectionName = null)
public function createTenantConnection(Tenant $tenant)
{
$connectionName = $connectionName ?? 'tenant'; // todo
$databaseName = $tenant->getDatabaseName();
$connectionName = $tenant->getConnectionName();
// Create the database connection.
$based_on = $this->app['config']['tenancy.database.based_on'] ?? $this->originalDefaultConnectionName;
@ -108,9 +109,9 @@ class DatabaseManager
$manager = $this->getTenantDatabaseManager($tenant);
if ($this->app['config']['tenancy.queue_database_creation'] ?? false) {
QueuedTenantDatabaseCreator::dispatch($this->app[$manager], $database, 'create');
QueuedTenantDatabaseCreator::dispatch($manager, $database, 'create');
} else {
return $this->app[$manager]->createDatabase($database);
return $manager->createDatabase($database);
}
}
@ -120,16 +121,16 @@ class DatabaseManager
$manager = $this->getTenantDatabaseManager($tenant);
if ($this->app['config']['tenancy.queue_database_creation'] ?? false) {
QueuedTenantDatabaseCreator::dispatch($this->app[$manager], $database, 'delete');
QueuedTenantDatabaseCreator::dispatch($manager, $database, 'delete');
} else {
return $this->app[$manager]->deleteDatabase($database);
return $manager->deleteDatabase($database);
}
}
protected function getTenantDatabaseManager(Tenant $tenant)
protected function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
{
$connection = $tenant->getConnectionName(); // todo
$driver = $this->getDriver($connection);
$this->createTenantConnection($tenant);
$driver = $this->getDriver($tenant->getConnectionName());
$databaseManagers = $this->app['config']['tenancy.database_managers'];
@ -137,6 +138,6 @@ class DatabaseManager
throw new DatabaseManagerNotRegisteredException($driver);
}
return $databaseManagers[$driver];
return $this->app[$databaseManagers[$driver]];
}
}

View file

@ -4,18 +4,31 @@ declare(strict_types=1);
namespace Stancl\Tenancy\StorageDrivers\Database;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Contracts\StorageDriver;
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
use Stancl\Tenancy\StorageDrivers\Database\DomainModel as Domains;
use Stancl\Tenancy\StorageDrivers\Database\Tenants as Tenants;
use Stancl\Tenancy\StorageDrivers\Database\TenantModel as Tenants;
use Stancl\Tenancy\Tenant;
class DatabaseStorageDriver implements StorageDriver
{
// todo write tests verifying that data is decoded and added to the array
/** @var Application */
protected $app;
/** @var Tenant The default tenant. */
protected $tenant;
public function __construct(Application $app)
{
$this->app = $app;
}
public function findByDomain(string $domain): Tenant
{
$id = $this->getTenantIdByDomain($domain);
@ -23,16 +36,16 @@ class DatabaseStorageDriver implements StorageDriver
throw new TenantCouldNotBeIdentifiedException($domain);
}
return $this->find($id);
return $this->findById($id);
}
public function findById(string $id): Tenant
{
return Tenant::fromStorage(Tenants::find($id)->decoded())
->withDomains(Domains::where('tenant_id', $id)->all()->only('domain')->toArray());
->withDomains(Domains::where('tenant_id', $id)->get()->only('domain')->toArray());
}
public function ensureTenantCanBeCreated(Tenant $tenant)
public function ensureTenantCanBeCreated(Tenant $tenant): void
{
// todo test this
if (Tenants::find($tenant->id)) {
@ -44,6 +57,13 @@ class DatabaseStorageDriver implements StorageDriver
}
}
public function withDefaultTenant(Tenant $tenant): self
{
$this->tenant = $tenant;
return $this;
}
public function getTenantIdByDomain(string $domain): ?string
{
return Domains::where('domain', $domain)->first()->tenant_id ?? null;
@ -64,9 +84,8 @@ class DatabaseStorageDriver implements StorageDriver
public function updateTenant(Tenant $tenant): void
{
// todo
// 1. update storage
// 2. update domains
Tenant::find($tenant->id)->putMany($tenant->data);
// todo update domains
}
public function deleteTenant(Tenant $tenant): void
@ -93,7 +112,7 @@ class DatabaseStorageDriver implements StorageDriver
*/
protected function tenant()
{
return $this->app[Tenant::class];
return $this->tenant ?? $this->app[Tenant::class];
}
public function get(string $key, Tenant $tenant = null)

View file

@ -15,6 +15,7 @@ class DomainModel extends Model
protected $primaryKey = 'id';
public $incrementing = false;
public $timestamps = false;
public $table = 'domains';
public function getConnectionName()
{

View file

@ -15,6 +15,7 @@ class TenantModel extends Model
protected $primaryKey = 'id';
public $incrementing = false;
public $timestamps = false;
public $table = 'tenants';
public static function dataColumn()
{

View file

@ -21,6 +21,9 @@ class RedisStorageDriver implements StorageDriver
/** @var Redis */
protected $redis;
/** @var Tenant The default tenant. */
protected $tenant;
public function __construct(Application $app, Redis $redis)
{
$this->app = $app;
@ -34,10 +37,17 @@ class RedisStorageDriver implements StorageDriver
*/
protected function tenant()
{
return $this->app[Tenant::class];
return $this->tenant ?? $this->app[Tenant::class];
}
public function ensureTenantCanBeCreated(Tenant $tenant)
public function withDefaultTenant(Tenant $tenant): self
{
$this->tenant = $tenant;
return $this;
}
public function ensureTenantCanBeCreated(Tenant $tenant): void
{
// todo
}

View file

@ -64,14 +64,13 @@ class TenancyServiceProvider extends ServiceProvider
$this->app->singleton($bootstrapper);
}
// todo are these necessary?
$this->app->singleton(Migrate::class, function ($app) {
$this->app->singleton(Commands\Migrate::class, function ($app) {
return new Commands\Migrate($app['migrator'], $app[DatabaseManager::class]);
});
$this->app->singleton(Rollback::class, function ($app) {
$this->app->singleton(Commands\Rollback::class, function ($app) {
return new Commands\Rollback($app['migrator'], $app[DatabaseManager::class]);
});
$this->app->singleton(Seed::class, function ($app) {
$this->app->singleton(Commands\Seed::class, function ($app) {
return new Commands\Seed($app['db'], $app[DatabaseManager::class]);
});

View file

@ -5,10 +5,13 @@ declare(strict_types=1);
namespace Stancl\Tenancy;
use ArrayAccess;
use Illuminate\Foundation\Application;
use Stancl\Tenancy\Contracts\StorageDriver;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
use Stancl\Tenancy\Exceptions\TenantStorageException;
// todo write tests for updating the tenant
/**
* @internal Class is subject to breaking changes in minor and patch versions.
*/
@ -30,6 +33,9 @@ class Tenant implements ArrayAccess
*/
public $domains = [];
/** @var Application */
protected $app;
/** @var StorageDriver */
protected $storage;
@ -46,16 +52,24 @@ class Tenant implements ArrayAccess
*/
protected $persisted = false;
public function __construct(StorageDriver $storage, TenantManager $tenantManager, UniqueIdentifierGenerator $idGenerator)
public function __construct(Application $app, StorageDriver $storage, TenantManager $tenantManager, UniqueIdentifierGenerator $idGenerator)
{
$this->storage = $storage;
$this->app = $app;
$this->storage = $storage->withDefaultTenant($this);
$this->manager = $tenantManager;
$this->idGenerator = $idGenerator;
}
public static function new(): self
public static function new(Application $app = null): self
{
return app(static::class);
$app = $app ?? app();
return new static(
$app,
$app[StorageDriver::class],
$app[TenantManager::class],
$app[UniqueIdentifierGenerator::class]
);
}
public static function fromStorage(array $data): self
@ -132,14 +146,14 @@ class Tenant implements ArrayAccess
public function save(): self
{
if (! $this->id) {
if (! isset($this->data['id'])) {
$this->generateId();
}
if ($this->persisted) {
$this->manager->createTenant($this);
} else {
$this->manager->updateTenant($this);
} else {
$this->manager->createTenant($this);
}
$this->persisted = true;
@ -178,7 +192,12 @@ class Tenant implements ArrayAccess
public function getDatabaseName()
{
return $this['_tenancy_db_name'] ?? $this->app['config']['tenancy.database.prefix'] . $this->uuid . $this->app['config']['tenancy.database.suffix'];
return $this['_tenancy_db_name'] ?? ($this->app['config']['tenancy.database.prefix'] . $this->id . $this->app['config']['tenancy.database.suffix']);
}
public function getConnectionName()
{
return $this['_tenancy_db_connection'] ?? 'tenant';
}
/**
@ -190,9 +209,11 @@ class Tenant implements ArrayAccess
public function get($keys)
{
if (is_array($keys)) {
if (array_intersect(array_keys($this->data), $keys)) { // if all keys are present in cache
if ((array_intersect(array_keys($this->data), $keys) === $keys) ||
! $this->persisted) { // if all keys are present in cache
return array_reduce($keys, function ($pairs, $key) {
$pairs[$key] = $this->data[$key];
$pairs[$key] = $this->data[$key] ?? null;
return $pairs;
}, []);
@ -201,11 +222,14 @@ class Tenant implements ArrayAccess
return $this->storage->getMany($keys);
}
if (! isset($this->data[$keys])) {
$this->data[$keys] = $this->storage->get($keys);
// single key
$key = $keys;
if (! isset($this->data[$key]) && $this->persisted) {
$this->data[$key] = $this->storage->get($key);
}
return $this->data[$keys];
return $this->data[$key];
}
public function put($key, $value = null): self
@ -227,8 +251,13 @@ class Tenant implements ArrayAccess
return $this;
}
public function __get($name)
public function __get($key)
{
return $this->get($name);
return $this->get($key);
}
public function __set($key, $value)
{
$this->data[$key] = $value;
}
}

View file

@ -210,9 +210,10 @@ class TenantManager
protected function bootstrapFeatures(): self
{
foreach ($this->app['config']['tenancy.features'] as $feature) {
$this->app[$feature]->bootstrap($this);
}
// todo this doesn't work
// foreach ($this->app['config']['tenancy.features'] as $feature) {
// $this->app[$feature]->bootstrap($this);
// }
return $this;
}
@ -225,7 +226,7 @@ class TenantManager
*/
public function tenancyBootstrappers($except = []): array
{
return array_key_diff($this->app['config']['tenancy.bootstrappers'], $except);
return array_diff_key($this->app['config']['tenancy.bootstrappers'], $except);
}
public function shouldMigrateAfterCreation(): bool

View file

@ -5,8 +5,9 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Redis;
use Stancl\Tenancy\StorageDrivers\DatabaseStorageDriver;
use Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver;
use Stancl\Tenancy\StorageDrivers\RedisStorageDriver;
use Stancl\Tenancy\Tenant;
abstract class TestCase extends \Orchestra\Testbench\TestCase
{
@ -40,19 +41,12 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
}
}
protected function tearDown(): void
public function createTenant($domains = ['test.localhost'])
{
// config(['database.default' => 'central']);
parent::tearDown();
Tenant::new()->withDomains($domains)->save();
}
public function createTenant($domain = 'localhost')
{
tenant()->create($domain);
}
public function initTenancy($domain = 'localhost')
public function initTenancy($domain = 'test.localhost')
{
return tenancy()->init($domain);
}
@ -112,13 +106,13 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
'tenancy.storage_driver' => RedisStorageDriver::class,
]);
tenancy()->storage = $app->make(RedisStorageDriver::class);
// tenancy()->storage = $app->make(RedisStorageDriver::class); // todo this shouldn't be necessary
} elseif (env('TENANCY_TEST_STORAGE_DRIVER', 'redis') === 'db') {
$app['config']->set([
'tenancy.storage_driver' => DatabaseStorageDriver::class,
]);
tenancy()->storage = $app->make(DatabaseStorageDriver::class);
// tenancy()->storage = $app->make(DatabaseStorageDriver::class); // todo this shouldn't be necessary
}
}