mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-15 05:24:04 +00:00
Merge branch 'master' into resource-syncing-refactor
This commit is contained in:
commit
728d2db321
12 changed files with 99 additions and 23 deletions
|
|
@ -48,6 +48,8 @@ return [
|
||||||
* SECURITY NOTE: Keep in mind that autoincrement IDs come with potential enumeration issues (such as tenant storage URLs).
|
* SECURITY NOTE: Keep in mind that autoincrement IDs come with potential enumeration issues (such as tenant storage URLs).
|
||||||
*
|
*
|
||||||
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\UUIDGenerator
|
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\UUIDGenerator
|
||||||
|
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\ULIDGenerator
|
||||||
|
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\UUIDv7Generator
|
||||||
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\RandomHexGenerator
|
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\RandomHexGenerator
|
||||||
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\RandomIntGenerator
|
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\RandomIntGenerator
|
||||||
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\RandomStringGenerator
|
* @see \Stancl\Tenancy\UniqueIdentifierGenerators\RandomStringGenerator
|
||||||
|
|
@ -311,7 +313,7 @@ return [
|
||||||
*
|
*
|
||||||
* Note: This will implicitly add your configured session store to the list of prefixed stores above.
|
* Note: This will implicitly add your configured session store to the list of prefixed stores above.
|
||||||
*/
|
*/
|
||||||
'scope_sessions' => true,
|
'scope_sessions' => in_array(env('SESSION_DRIVER'), ['redis', 'memcached', 'dynamodb', 'apc'], true),
|
||||||
|
|
||||||
'tag_base' => 'tenant', // This tag_base, followed by the tenant_id, will form a tag that will be applied on each cache call.
|
'tag_base' => 'tenant', // This tag_base, followed by the tenant_id, will form a tag that will be applied on each cache call.
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -102,14 +102,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
if ($this->config->get('tenancy.cache.scope_sessions', true)) {
|
if ($this->config->get('tenancy.cache.scope_sessions', true)) {
|
||||||
// These are the only cache driven session backends (see Laravel's config/session.php)
|
// These are the only cache driven session backends (see Laravel's config/session.php)
|
||||||
if (! in_array($this->config->get('session.driver'), ['redis', 'memcached', 'dynamodb', 'apc'], true)) {
|
if (! in_array($this->config->get('session.driver'), ['redis', 'memcached', 'dynamodb', 'apc'], true)) {
|
||||||
if (app()->environment('production')) {
|
throw new Exception('Session driver [' . $this->config->get('session.driver') . '] cannot be scoped by tenancy.cache.scope_sessions');
|
||||||
// We only throw this exception in prod to make configuration a little easier. Developers
|
|
||||||
// may have scope_sessions set to true while using different session drivers e.g. in tests.
|
|
||||||
// Previously we just silently ignored this, however since session scoping is of high importance
|
|
||||||
// in production, we make sure to notify the developer, by throwing an exception, that session
|
|
||||||
// scoping isn't happening as expected/configured due to an incompatible session driver.
|
|
||||||
throw new Exception('Session driver [' . $this->config->get('session.driver') . '] cannot be scoped by tenancy.cache.scope_session');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Scoping sessions using this bootstrapper implicitly adds the session store to $names
|
// Scoping sessions using this bootstrapper implicitly adds the session store to $names
|
||||||
$names[] = $this->getSessionCacheStoreName();
|
$names[] = $this->getSessionCacheStoreName();
|
||||||
|
|
|
||||||
|
|
@ -63,13 +63,17 @@ class DatabaseCacheBootstrapper implements TenancyBootstrapper
|
||||||
$stores = $this->scopedStoreNames();
|
$stores = $this->scopedStoreNames();
|
||||||
|
|
||||||
foreach ($stores as $storeName) {
|
foreach ($stores as $storeName) {
|
||||||
$this->originalConnections[$storeName] = $this->config->get("cache.stores.{$storeName}.connection");
|
$this->originalConnections[$storeName] = $this->config->get("cache.stores.{$storeName}.connection") ?? config('tenancy.database.central_connection');
|
||||||
$this->originalLockConnections[$storeName] = $this->config->get("cache.stores.{$storeName}.lock_connection");
|
$this->originalLockConnections[$storeName] = $this->config->get("cache.stores.{$storeName}.lock_connection") ?? config('tenancy.database.central_connection');
|
||||||
|
|
||||||
$this->config->set("cache.stores.{$storeName}.connection", 'tenant');
|
$this->config->set("cache.stores.{$storeName}.connection", 'tenant');
|
||||||
$this->config->set("cache.stores.{$storeName}.lock_connection", 'tenant');
|
$this->config->set("cache.stores.{$storeName}.lock_connection", 'tenant');
|
||||||
|
|
||||||
$this->cache->purge($storeName);
|
/** @var DatabaseStore $store */
|
||||||
|
$store = $this->cache->store($storeName)->getStore();
|
||||||
|
|
||||||
|
$store->setConnection(DB::connection('tenant'));
|
||||||
|
$store->setLockConnection(DB::connection('tenant'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$adjustGlobalCacheManager) {
|
if (static::$adjustGlobalCacheManager) {
|
||||||
|
|
@ -78,8 +82,8 @@ class DatabaseCacheBootstrapper implements TenancyBootstrapper
|
||||||
// *from here* being executed repeatedly in a loop on reinitialization. For that reason we do not do that
|
// *from here* being executed repeatedly in a loop on reinitialization. For that reason we do not do that
|
||||||
// (this is our only use of $adjustCacheManagerUsing anyway) but ideally at some point we'd have a better solution.
|
// (this is our only use of $adjustCacheManagerUsing anyway) but ideally at some point we'd have a better solution.
|
||||||
$originalConnections = array_combine($stores, array_map(fn (string $storeName) => [
|
$originalConnections = array_combine($stores, array_map(fn (string $storeName) => [
|
||||||
'connection' => $this->originalConnections[$storeName] ?? config('tenancy.database.central_connection'),
|
'connection' => $this->originalConnections[$storeName],
|
||||||
'lockConnection' => $this->originalLockConnections[$storeName] ?? config('tenancy.database.central_connection'),
|
'lockConnection' => $this->originalLockConnections[$storeName],
|
||||||
], $stores));
|
], $stores));
|
||||||
|
|
||||||
TenancyServiceProvider::$adjustCacheManagerUsing = static function (CacheManager $manager) use ($originalConnections) {
|
TenancyServiceProvider::$adjustCacheManagerUsing = static function (CacheManager $manager) use ($originalConnections) {
|
||||||
|
|
@ -100,7 +104,11 @@ class DatabaseCacheBootstrapper implements TenancyBootstrapper
|
||||||
$this->config->set("cache.stores.{$storeName}.connection", $originalConnection);
|
$this->config->set("cache.stores.{$storeName}.connection", $originalConnection);
|
||||||
$this->config->set("cache.stores.{$storeName}.lock_connection", $this->originalLockConnections[$storeName]);
|
$this->config->set("cache.stores.{$storeName}.lock_connection", $this->originalLockConnections[$storeName]);
|
||||||
|
|
||||||
$this->cache->purge($storeName);
|
/** @var DatabaseStore $store */
|
||||||
|
$store = $this->cache->store($storeName)->getStore();
|
||||||
|
|
||||||
|
$store->setConnection(DB::connection($this->originalConnections[$storeName]));
|
||||||
|
$store->setLockConnection(DB::connection($this->originalLockConnections[$storeName]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TenancyServiceProvider::$adjustCacheManagerUsing = null;
|
TenancyServiceProvider::$adjustCacheManagerUsing = null;
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,15 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$path = $suffix
|
||||||
|
? $this->tenantStoragePath($suffix) . '/framework/cache'
|
||||||
|
: $this->originalStoragePath . '/framework/cache';
|
||||||
|
|
||||||
|
if (! is_dir($path)) {
|
||||||
|
// Create tenant framework/cache directory if it does not exist
|
||||||
|
mkdir($path, 0750, true);
|
||||||
|
}
|
||||||
|
|
||||||
if ($suffix === false) {
|
if ($suffix === false) {
|
||||||
$this->app->useStoragePath($this->originalStoragePath);
|
$this->app->useStoragePath($this->originalStoragePath);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -211,7 +220,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
if (! is_dir($path)) {
|
if (! is_dir($path)) {
|
||||||
// Create tenant framework/sessions directory if it does not exist
|
// Create tenant framework/sessions directory if it does not exist
|
||||||
mkdir($path, 0755, true);
|
mkdir($path, 0750, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->app['config']['session.files'] = $path;
|
$this->app['config']['session.files'] = $path;
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,25 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Listeners;
|
namespace Stancl\Tenancy\Listeners;
|
||||||
|
|
||||||
use Stancl\Tenancy\Events\TenantCreated;
|
use Stancl\Tenancy\Events\Contracts\TenantEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to manually create framework directories in the tenant storage when storage_path() is scoped.
|
||||||
|
*
|
||||||
|
* Useful when using real-time facades which use the framework/cache directory.
|
||||||
|
*
|
||||||
|
* Generally not needed anymore as the directory is also created by the FilesystemTenancyBootstrapper.
|
||||||
|
*/
|
||||||
class CreateTenantStorage
|
class CreateTenantStorage
|
||||||
{
|
{
|
||||||
public function handle(TenantCreated $event): void
|
public function handle(TenantEvent $event): void
|
||||||
{
|
{
|
||||||
$storage_path = tenancy()->run($event->tenant, fn () => storage_path());
|
$storage_path = tenancy()->run($event->tenant, fn () => storage_path());
|
||||||
$cache_path = "$storage_path/framework/cache";
|
$cache_path = "$storage_path/framework/cache";
|
||||||
|
|
||||||
if (! is_dir($cache_path)) {
|
if (! is_dir($cache_path)) {
|
||||||
// Create the tenant's storage directory and /framework/cache within (used for e.g. real-time facades)
|
// Create the tenant's storage directory and /framework/cache within (used for e.g. real-time facades)
|
||||||
mkdir($cache_path, 0777, true);
|
mkdir($cache_path, 0750, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Listeners;
|
namespace Stancl\Tenancy\Listeners;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Stancl\Tenancy\Events\DeletingTenant;
|
use Stancl\Tenancy\Events\Contracts\TenantEvent;
|
||||||
|
|
||||||
class DeleteTenantStorage
|
class DeleteTenantStorage
|
||||||
{
|
{
|
||||||
public function handle(DeletingTenant $event): void
|
public function handle(TenantEvent $event): void
|
||||||
{
|
{
|
||||||
$path = tenancy()->run($event->tenant, fn () => storage_path());
|
$path = tenancy()->run($event->tenant, fn () => storage_path());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use Illuminate\Support\Str;
|
||||||
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a UUID for the tenant key.
|
* Generates a ULID for the tenant key.
|
||||||
*/
|
*/
|
||||||
class ULIDGenerator implements UniqueIdentifierGenerator
|
class ULIDGenerator implements UniqueIdentifierGenerator
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use Ramsey\Uuid\Uuid;
|
||||||
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a UUID for the tenant key.
|
* Generates a UUIDv4 for the tenant key.
|
||||||
*/
|
*/
|
||||||
class UUIDGenerator implements UniqueIdentifierGenerator
|
class UUIDGenerator implements UniqueIdentifierGenerator
|
||||||
{
|
{
|
||||||
|
|
|
||||||
20
src/UniqueIdentifierGenerators/UUIDv7Generator.php
Normal file
20
src/UniqueIdentifierGenerators/UUIDv7Generator.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\UniqueIdentifierGenerators;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a UUIDv7 for the tenant key.
|
||||||
|
*/
|
||||||
|
class UUIDv7Generator implements UniqueIdentifierGenerator
|
||||||
|
{
|
||||||
|
public static function generate(Model $model): string|int
|
||||||
|
{
|
||||||
|
return Str::uuid7()->toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -200,3 +200,24 @@ test('tenant storage can get deleted after the tenant when DeletingTenant listen
|
||||||
|
|
||||||
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the framework/cache directory is created when storage_path is scoped', function (bool $suffixStoragePath) {
|
||||||
|
config([
|
||||||
|
'tenancy.bootstrappers' => [
|
||||||
|
FilesystemTenancyBootstrapper::class,
|
||||||
|
],
|
||||||
|
'tenancy.filesystem.suffix_storage_path' => $suffixStoragePath
|
||||||
|
]);
|
||||||
|
|
||||||
|
$centralStoragePath = storage_path();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant = Tenant::create());
|
||||||
|
|
||||||
|
if ($suffixStoragePath) {
|
||||||
|
expect(storage_path('framework/cache'))->toBe($centralStoragePath . "/tenant{$tenant->id}/framework/cache");
|
||||||
|
expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeTrue();
|
||||||
|
} else {
|
||||||
|
expect(storage_path('framework/cache'))->toBe($centralStoragePath . '/framework/cache');
|
||||||
|
expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeFalse();
|
||||||
|
}
|
||||||
|
})->with([true, false]);
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ test('file sessions are separated', function (bool $scopeSessions) {
|
||||||
|
|
||||||
if ($scopeSessions) {
|
if ($scopeSessions) {
|
||||||
expect($sessionPath())->toBe(storage_path('tenant' . $tenant->getTenantKey() . '/framework/sessions'));
|
expect($sessionPath())->toBe(storage_path('tenant' . $tenant->getTenantKey() . '/framework/sessions'));
|
||||||
|
expect(is_dir(storage_path('tenant' . $tenant->getTenantKey() . '/framework/sessions')))->toBeTrue();
|
||||||
} else {
|
} else {
|
||||||
expect($sessionPath())->toBe(storage_path('framework/sessions'));
|
expect($sessionPath())->toBe(storage_path('framework/sessions'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ use Stancl\Tenancy\UniqueIdentifierGenerators\RandomHexGenerator;
|
||||||
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomIntGenerator;
|
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomIntGenerator;
|
||||||
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomStringGenerator;
|
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomStringGenerator;
|
||||||
use Stancl\Tenancy\UniqueIdentifierGenerators\ULIDGenerator;
|
use Stancl\Tenancy\UniqueIdentifierGenerators\ULIDGenerator;
|
||||||
|
use Stancl\Tenancy\UniqueIdentifierGenerators\UUIDv7Generator;
|
||||||
|
|
||||||
use function Stancl\Tenancy\Tests\pest;
|
use function Stancl\Tenancy\Tests\pest;
|
||||||
|
|
||||||
|
|
@ -94,6 +95,20 @@ test('ulid ids are supported', function () {
|
||||||
expect($tenant2->id > $tenant1->id)->toBeTrue();
|
expect($tenant2->id > $tenant1->id)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uuidv7 ids are supported', function () {
|
||||||
|
app()->bind(UniqueIdentifierGenerator::class, UUIDv7Generator::class);
|
||||||
|
|
||||||
|
$tenant1 = Tenant::create();
|
||||||
|
expect($tenant1->id)->toBeString();
|
||||||
|
expect(strlen($tenant1->id))->toBe(36);
|
||||||
|
|
||||||
|
$tenant2 = Tenant::create();
|
||||||
|
expect($tenant2->id)->toBeString();
|
||||||
|
expect(strlen($tenant2->id))->toBe(36);
|
||||||
|
|
||||||
|
expect($tenant2->id > $tenant1->id)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
test('hex ids are supported', function () {
|
test('hex ids are supported', function () {
|
||||||
app()->bind(UniqueIdentifierGenerator::class, RandomHexGenerator::class);
|
app()->bind(UniqueIdentifierGenerator::class, RandomHexGenerator::class);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue