mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 16:44:04 +00:00
Refactor DB cache bootstrapper, update tests accordingly
This commit is contained in:
parent
bc846391ea
commit
872bf9df72
2 changed files with 135 additions and 16 deletions
|
|
@ -13,40 +13,73 @@ use Stancl\Tenancy\Contracts\Tenant;
|
||||||
* This bootstrapper allows cache to be stored in the tenant databases by switching
|
* This bootstrapper allows cache to be stored in the tenant databases by switching
|
||||||
* the database cache store's (and cache locks) connection.
|
* the database cache store's (and cache locks) connection.
|
||||||
*
|
*
|
||||||
* Intended to be used with the 'database' cache store, instead of CacheTenancyBootstrapper.
|
* Intended to be used with database driver-based cache stores, instead of CacheTenancyBootstrapper.
|
||||||
*
|
*
|
||||||
* On bootstrap(), the database cache store's connection is set to 'tenant'
|
* On bootstrap(), all database cache stores' connections are set to 'tenant'
|
||||||
* and the database cache store is purged from the CacheManager's resolved stores.
|
* and the database cache stores are purged from the CacheManager's resolved stores.
|
||||||
* This forces the manager to resolve a new instance of the database store created with the 'tenant' DB connection on the next cache operation.
|
* This forces the manager to resolve new instances of the database stores created with the 'tenant' DB connection on the next cache operation.
|
||||||
*
|
*
|
||||||
* On revert(), the cache store's connection is reverted to the originally used one (usually 'central'), and again,
|
* On revert(), the cache stores' connections are reverted to the originally used ones (usually 'central'), and again,
|
||||||
* the database cache store is purged from the CacheManager's resolved stores so that the originally used one is resolved on the next cache operation.
|
* the database cache stores are purged from the CacheManager's resolved stores so that the originally used ones are resolved on the next cache operation.
|
||||||
*/
|
*/
|
||||||
class DatabaseCacheBootstrapper implements TenancyBootstrapper
|
class DatabaseCacheBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Cache stores to process. If null, all stores with 'database' driver will be processed.
|
||||||
|
* If array, only the specified stores will be processed (with driver validation).
|
||||||
|
*/
|
||||||
|
public static array|null $stores = null;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Repository $config,
|
protected Repository $config,
|
||||||
protected CacheManager $cache,
|
protected CacheManager $cache,
|
||||||
protected string|null $originalConnection = null,
|
protected array $originalConnections = [],
|
||||||
protected string|null $originalLockConnection = null
|
protected array $originalLockConnections = []
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant): void
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
$this->originalConnection = $this->config->get('cache.stores.database.connection');
|
$stores = $this->getDatabaseCacheStores();
|
||||||
$this->originalLockConnection = $this->config->get('cache.stores.database.lock_connection');
|
|
||||||
|
|
||||||
$this->config->set('cache.stores.database.connection', 'tenant');
|
foreach ($stores as $storeName) {
|
||||||
$this->config->set('cache.stores.database.lock_connection', 'tenant');
|
$this->originalConnections[$storeName] = $this->config->get("cache.stores.{$storeName}.connection");
|
||||||
|
$this->originalLockConnections[$storeName] = $this->config->get("cache.stores.{$storeName}.lock_connection");
|
||||||
|
|
||||||
$this->cache->purge('database');
|
$this->config->set("cache.stores.{$storeName}.connection", 'tenant');
|
||||||
|
$this->config->set("cache.stores.{$storeName}.lock_connection", 'tenant');
|
||||||
|
|
||||||
|
$this->cache->purge($storeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert(): void
|
public function revert(): void
|
||||||
{
|
{
|
||||||
$this->config->set('cache.stores.database.connection', $this->originalConnection);
|
foreach ($this->originalConnections as $storeName => $originalConnection) {
|
||||||
$this->config->set('cache.stores.database.lock_connection', $this->originalLockConnection);
|
$this->config->set("cache.stores.{$storeName}.connection", $originalConnection);
|
||||||
|
$this->config->set("cache.stores.{$storeName}.lock_connection", $this->originalLockConnections[$storeName]);
|
||||||
|
|
||||||
$this->cache->purge('database');
|
$this->cache->purge($storeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the names of cache stores that use the database driver.
|
||||||
|
*/
|
||||||
|
protected function getDatabaseCacheStores(): array
|
||||||
|
{
|
||||||
|
// Get all stores specified in the static $stores property.
|
||||||
|
// If they don't have the database driver, ignore them.
|
||||||
|
if (static::$stores !== null) {
|
||||||
|
return array_filter(static::$stores, function ($storeName) {
|
||||||
|
$store = $this->config->get("cache.stores.{$storeName}");
|
||||||
|
|
||||||
|
return $store && ($store['driver'] ?? null) === 'database';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all stores with database driver if $stores is null
|
||||||
|
return array_keys(array_filter($this->config->get('cache.stores', []), function ($store) {
|
||||||
|
return ($store['driver'] ?? null) === 'database';
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,3 +143,89 @@ test('cache is separated correctly when using DatabaseCacheBootstrapper', functi
|
||||||
expect(Cache::get('foo'))->toBe('central');
|
expect(Cache::get('foo'))->toBe('central');
|
||||||
expect($getCacheUsingDbQuery('foo'))->toContain('central');
|
expect($getCacheUsingDbQuery('foo'))->toContain('central');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('DatabaseCacheBootstrapper auto-detects all database driver stores by default', function() {
|
||||||
|
// Configure multiple stores with different drivers
|
||||||
|
config([
|
||||||
|
'cache.stores.database' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'connection' => 'central',
|
||||||
|
'table' => 'cache',
|
||||||
|
],
|
||||||
|
'cache.stores.sessions' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'connection' => 'central',
|
||||||
|
'table' => 'sessions_cache',
|
||||||
|
],
|
||||||
|
'cache.stores.redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => 'default',
|
||||||
|
],
|
||||||
|
'cache.stores.file' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
'path' => '/foo/bar',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Here, we're using auto-detection (default behavior)
|
||||||
|
expect(config('cache.stores.database.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.sessions.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.redis.connection'))->toBe('default');
|
||||||
|
expect(config('cache.stores.file.path'))->toBe('/foo/bar');
|
||||||
|
|
||||||
|
tenancy()->initialize(Tenant::create());
|
||||||
|
|
||||||
|
// Using auto-detection (default behavior),
|
||||||
|
// all database driver stores should be configured,
|
||||||
|
// and stores with non-database drivers are ignored.
|
||||||
|
expect(config('cache.stores.database.connection'))->toBe('tenant');
|
||||||
|
expect(config('cache.stores.sessions.connection'))->toBe('tenant');
|
||||||
|
expect(config('cache.stores.redis.connection'))->toBe('default'); // unchanged
|
||||||
|
expect(config('cache.stores.file.path'))->toBe('/foo/bar'); // unchanged
|
||||||
|
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
// All database stores should be reverted, others unchanged
|
||||||
|
expect(config('cache.stores.database.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.sessions.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.redis.connection'))->toBe('default');
|
||||||
|
expect(config('cache.stores.file.path'))->toBe('/foo/bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manual $stores configuration takes precedence over auto-detection', function() {
|
||||||
|
// Configure multiple database stores
|
||||||
|
config([
|
||||||
|
'cache.stores.sessions' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'connection' => 'central',
|
||||||
|
'table' => 'sessions_cache',
|
||||||
|
],
|
||||||
|
'cache.stores.redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => 'default',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Specific store overrides (including non-database stores)
|
||||||
|
DatabaseCacheBootstrapper::$stores = ['sessions', 'redis']; // Note: excludes 'database'
|
||||||
|
|
||||||
|
expect(config('cache.stores.database.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.sessions.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.redis.connection'))->toBe('default');
|
||||||
|
|
||||||
|
tenancy()->initialize(Tenant::create());
|
||||||
|
|
||||||
|
// Manual config takes precedence: only 'sessions' is configured
|
||||||
|
// - redis filtered out by driver check
|
||||||
|
// - database store not included in $stores
|
||||||
|
expect(config('cache.stores.database.connection'))->toBe('central'); // Excluded in manual config
|
||||||
|
expect(config('cache.stores.sessions.connection'))->toBe('tenant'); // Included and is database driver
|
||||||
|
expect(config('cache.stores.redis.connection'))->toBe('default'); // Included but filtered out (not database driver)
|
||||||
|
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
// Only the manually configured stores' config will be reverted
|
||||||
|
expect(config('cache.stores.database.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.sessions.connection'))->toBe('central');
|
||||||
|
expect(config('cache.stores.redis.connection'))->toBe('default');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue