From 4b823fca7df24f6b701c9b5cc31fb057bfd9e9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Tue, 5 Aug 2025 21:00:56 +0200 Subject: [PATCH] Configure globalCache's DB stores to use central connection instead of default connection every time it's reinstantiated --- .github/workflows/ci.yml | 4 +-- .../DatabaseCacheBootstrapper.php | 18 ++++++++++++ src/TenancyServiceProvider.php | 29 ++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91699f08..a22c1a94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: include: - - laravel: "^12.0" + - laravel: "dev-dbstore-setconnection" # todo0 revert along with require (8 lines below) php: "8.4" steps: @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v2 - name: Install Composer dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update + composer require "stancl/framework:${{ matrix.laravel }}" --no-interaction --no-update composer update --prefer-dist --no-interaction - name: Run tests if: ${{ ! env.ACT }} diff --git a/src/Bootstrappers/DatabaseCacheBootstrapper.php b/src/Bootstrappers/DatabaseCacheBootstrapper.php index 17886d14..09859eff 100644 --- a/src/Bootstrappers/DatabaseCacheBootstrapper.php +++ b/src/Bootstrappers/DatabaseCacheBootstrapper.php @@ -7,8 +7,10 @@ namespace Stancl\Tenancy\Bootstrappers; use Exception; use Illuminate\Cache\CacheManager; use Illuminate\Config\Repository; +use Illuminate\Support\Facades\DB; use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; +use Stancl\Tenancy\TenancyServiceProvider; /** * This bootstrapper allows cache to be stored in the tenant databases by switching @@ -55,6 +57,22 @@ class DatabaseCacheBootstrapper implements TenancyBootstrapper $this->cache->purge($storeName); } + + // Preferably we'd try to respect the original value of this static property -- store it in a variable, + // pull it into the closure, and execute it there. But such a naive approach would lead to existing callbacks + // *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. + TenancyServiceProvider::$adjustCacheManagerUsing = function (CacheManager $manager) use ($stores) { + foreach ($stores as $storeName) { + $manager->store($storeName)->getStore()->setConnection( + DB::connection($this->originalConnections[$storeName] ?? config('tenancy.database.central_connection')) + ); + + $manager->store($storeName)->getStore()->setLockConnection( + DB::connection($this->originalLockConnections[$storeName] ?? config('tenancy.database.central_connection')) + ); + } + }; } public function revert(): void diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 22a81624..1c2e1abe 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -23,6 +23,9 @@ class TenancyServiceProvider extends ServiceProvider public static bool $registerForgetTenantParameterListener = true; public static bool $migrateFreshOverride = true; + /** @internal */ + public static Closure|null $adjustCacheManagerUsing = null; + /* Register services. */ public function register(): void { @@ -80,8 +83,32 @@ class TenancyServiceProvider extends ServiceProvider return new Commands\Seed($app['db']); }); + // todo0 check how the caching in the facade affects our logic here + // todo0 check what happens if globalCache is injected - it may be + // problematic if it's injected before adjustCacheManagerUsing + // was used + $this->app->bind('globalCache', function ($app) { - return new CacheManager($app); + // We create a separate CacheManager to be used for "global" cache -- cache that + // is always central, regardless of the current context. + // + // Importantly, we use a regular binding here, not a singleton. Thanks to that, + // any time we resolve this cache manager, we get a *fresh* instance -- an instance + // that was not affected by any scoping logic. + // + // 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. + $manager = new CacheManager($app); + + if (static::$adjustCacheManagerUsing !== null) { + (static::$adjustCacheManagerUsing)($manager); + } + + return $manager; }); }