Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Bootstrappers/DatabaseCacheBootstrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
*
* Notably, this bootstrapper sets TenancyServiceProvider::$adjustCacheManagerUsing to a callback
* that ensures all affected stores still use the central connection when accessed via global cache
* (typicaly the GlobalCache facade or global_cache() helper).
* (typically the GlobalCache facade or global_cache() helper), even though this bootstrapper explicitly
* sets the connection to tenant for all scoped cache stores. TenancyServiceProvider::makeDatabaseCacheStoresCentral()
* cannot fix globalCache on its own because it reads 'tenant' from config (set by this bootstrapper), not null,
* so the callback is still needed to correct the connection to central for globalCache.
*/
class DatabaseCacheBootstrapper implements TenancyBootstrapper
{
Expand Down
52 changes: 47 additions & 5 deletions src/TenancyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

use Closure;
use Illuminate\Cache\CacheManager;
use Illuminate\Cache\DatabaseStore;
use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Routing\Events\RouteMatched;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
Expand Down Expand Up @@ -86,14 +88,26 @@ public function register(): void
// This works great for cache stores that are *directly* scoped, like Redis or
// any other tagged or prefixed stores, but it doesn't work for the database driver.
//
// When we use the DatabaseTenancyBootstrapper, it changes the default connection,
// and therefore the connection of the database store that will be created when
// this new CacheManager is instantiated again.
// When DatabaseTenancyBootstrapper is used, it changes the default DB connection
// to 'tenant'. A freshly created CacheManager would therefore instantiate database
// stores with the tenant connection.
//
// For that reason, we also adjust the relevant stores on this new CacheManager
// using the callback below. It is set by DatabaseCacheBootstrapper.
// For that reason, we adjust the relevant stores on this new CacheManager
// using the makeDatabaseCacheStoresCentral() method and the $adjustCacheManagerUsing callback below
// (set by DatabaseCacheBootstrapper).
$manager = new CacheManager($app);

// When DatabaseTenancyBootstrapper is used, database stores whose 'connection'
// config is null fall back to the default DB connection ('tenant'). Reset each
// such store to its explicitly configured connection, or fall back to central.
$this->makeDatabaseCacheStoresCentral($manager);

// DatabaseCacheBootstrapper explicitly writes 'tenant' into each store's 'connection'
// config. makeDatabaseCacheStoresCentral() above would then read 'tenant' as the
// configured value (not null) and use it directly, so the central connection fallback
// wouldn't be used.
//
// This callback is used to correct those connections back to central for globalCache.
if (static::$adjustCacheManagerUsing !== null) {
(static::$adjustCacheManagerUsing)($manager);
}
Expand All @@ -102,6 +116,34 @@ public function register(): void
});
}

/**
* Ensure globalCache uses the central connection for database cache stores.
*
* A freshly built CacheManager creates database stores using the current default connection, which
* DatabaseTenancyBootstrapper switches to the tenant connection. Since global cache should always be
* central, reset those stores back to their configured connection, falling back to the central one.
*/
protected function makeDatabaseCacheStoresCentral(CacheManager $manager): void
{
$centralConnection = $this->app['config']['tenancy.database.central_connection'];

foreach ($this->app['config']['cache.stores'] ?? [] as $name => $store) {
$notAValidDatabaseStore = ! is_array($store) || ($store['driver'] ?? null) !== 'database';

if ($notAValidDatabaseStore) {
continue;
}

/** @var DatabaseStore $databaseStore */
$databaseStore = $manager->store($name)->getStore();

// If $store['connection'] is null, it defaults to the default DB connection (which may be tenant).
// Fall back to the central connection to keep the global cache central.
$databaseStore->setConnection(DB::connection($store['connection'] ?? $centralConnection));
$databaseStore->setLockConnection(DB::connection($store['lock_connection'] ?? $store['connection'] ?? $centralConnection));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

/* Bootstrap services. */
public function boot(): void
{
Expand Down
3 changes: 3 additions & 0 deletions tests/CachedTenantResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
use Stancl\Tenancy\TenancyServiceProvider;
use function Stancl\Tenancy\Tests\pest;
use function Stancl\Tenancy\Tests\withCacheTables;
use function Stancl\Tenancy\Tests\withTenantDatabases;

beforeEach($cleanup = function () {
Tenant::$extraCustomColumns = [];
TenancyServiceProvider::$adjustCacheManagerUsing = null;
});

afterEach($cleanup);
Expand Down Expand Up @@ -165,6 +167,7 @@
['redis', [CacheTenancyBootstrapper::class]],
['redis', [CacheTagsBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, CacheTenancyBootstrapper::class]],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
]);

test('cache is invalidated when the tenant is deleted', function (string $resolver, bool $configureTenantModelColumn) {
Expand Down
8 changes: 8 additions & 0 deletions tests/GlobalCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\TenancyServiceProvider;

use function Stancl\Tenancy\Tests\withCacheTables;
use function Stancl\Tenancy\Tests\withTenantDatabases;

beforeEach(function () {
TenancyServiceProvider::$adjustCacheManagerUsing = null;

config([
'cache.default' => 'redis',
'tenancy.cache.stores' => ['redis'],
Expand All @@ -29,6 +32,10 @@
withCacheTables();
});

afterEach(function () {
TenancyServiceProvider::$adjustCacheManagerUsing = null;
});

test('global cache manager stores data in global cache', function (string $store, array $bootstrappers) {
config([
'cache.default' => $store,
Expand Down Expand Up @@ -165,6 +172,7 @@
['redis', [CacheTagsBootstrapper::class]],
['redis', [CacheTenancyBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, CacheTenancyBootstrapper::class]],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
])->with([
'helper',
'facade',
Expand Down
Loading