Skip to content

Commit ecf0312

Browse files
lukinovecstancl
andauthored
Make globalCache always use central conn with DB cache stores (#1462)
`globalCache` should always use the central connection, but when using a `database`-driver cache store with `DatabaseTenancyBootstrapper`, it does not (with the exception of `DatabaseCacheBootstrapper`, explained below). `globalCache` creates a fresh `CacheManager` each time it's resolved (it's a `bind`, not a `singleton`). A freshly-created manager builds its database stores using the current default DB connection. When `DatabaseTenancyBootstrapper` is active, that default is `tenant`. So `globalCache` in tenant context points at the tenant DB. Specifically, `CachedTenantResolver` stores cached tenant lookups via `globalCache`. When a domain is deleted in tenant context, the invalidation logic calls `globalCache->forget(...)`, but that hits the tenant DB, while the resolver cache entry is in the central DB. `globalCache->forget(...)` doesn't actually do anything in that case. With `DatabaseCacheBootstrapper`, this is already handled. `globalCache` is always central because it sets `TenancyServiceProvider::$adjustCacheManagerUsing` to a callback that explicitly restores the central connection on `globalCache`'s stores. To fix this, after constructing the fresh CacheManager in the globalCache binding, explicitly set the connection of every database-driver store to its configured connection value, **falling back to central_connection** when the config value is null (null value = inherits whatever the current default DB connection is). This is sufficient for `CacheTenancyBootstrapper` and any other bootstrapper that doesn't explicitly set the store's DB connection. For `DatabaseCacheBootstrapper` specifically, this alone is not enough since it explicitly sets the store's config connection to 'tenant'. That's why DatabaseCacheBootstrapper's `$adjustCacheManagerUsing` callback runs after and overrides those stores back to the original (central) connection. > In short: `makeDatabaseCacheStoresCentral()` handles stores with a `null` connection config (falls back to central). `$adjustCacheManagerUsing` handles the `DatabaseCacheBootstrapper` case where the config is explicitly set to 'tenant'. Added datasets that use the database cache store + CacheTenancyBootstrapper to the relevant tests (globalCache and invalidation) to test regression (0cf7043), and the changes mentioned above (5e65c67) make these tests pass. --------- Co-authored-by: Samuel Stancl <samuel@archte.ch>
1 parent 04da9c8 commit ecf0312

5 files changed

Lines changed: 38 additions & 7 deletions

File tree

src/Bootstrappers/CacheTenancyBootstrapper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616

1717
/**
1818
* Makes cache tenant-aware by applying a prefix.
19+
*
20+
* Using this bootstrapper together with DatabaseTenancyBootstrapper
21+
* with a database cache store will result in "double scoping". The store will be scoped
22+
* by the DB connection (entries will go into the tenant's database) *and* by the prefix.
23+
* This is harmless in most cases, but is important to be aware of.
24+
*
25+
* If you only use database cache stores, consider using DatabaseCacheBootstrapper instead.
26+
*
27+
* @see Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper
1928
*/
2029
class CacheTenancyBootstrapper implements TenancyBootstrapper
2130
{

src/Bootstrappers/DatabaseCacheBootstrapper.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@
2121
*
2222
* By default, this bootstrapper scopes ALL cache stores that use the database driver. If you only
2323
* want to scope SOME stores, set the static $stores property to an array of names of the stores
24-
* you want to scope. These stores must use 'database' as their driver.
24+
* you want to scope. Those stores must use 'database' as their driver.
2525
*
2626
* Notably, this bootstrapper sets TenancyServiceProvider::$adjustCacheManagerUsing to a callback
2727
* that ensures all affected stores still use the central connection when accessed via global cache
28-
* (typicaly the GlobalCache facade or global_cache() helper).
28+
* (typically the GlobalCache facade or global_cache() helper). The code in TenancyServiceProvider
29+
* that uses `extend()` callbacks to make database stores on the global cache manager use the central
30+
* connection only corrects stores scoped by the Database*Tenancy*Bootstrapper. This bootstrapper
31+
* also changes the stores' connection in the *config* to 'tenant' which doesn't let that callback
32+
* change the connection back to central on the global cache manager.
2933
*/
3034
class DatabaseCacheBootstrapper implements TenancyBootstrapper
3135
{

src/TenancyServiceProvider.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,30 @@ public function register(): void
8686
// This works great for cache stores that are *directly* scoped, like Redis or
8787
// any other tagged or prefixed stores, but it doesn't work for the database driver.
8888
//
89-
// When we use the DatabaseTenancyBootstrapper, it changes the default connection,
90-
// and therefore the connection of the database store that will be created when
91-
// this new CacheManager is instantiated again.
89+
// When DatabaseTenancyBootstrapper is used, it changes the default DB connection
90+
// to 'tenant'. A freshly created CacheManager would therefore instantiate database
91+
// stores with the tenant connection.
9292
//
93-
// For that reason, we also adjust the relevant stores on this new CacheManager
94-
// using the callback below. It is set by DatabaseCacheBootstrapper.
93+
// For that reason, we override the 'database' driver creator on this manager so that
94+
// database stores are built with the central connection, and we run the
95+
// $adjustCacheManagerUsing callback below (set by DatabaseCacheBootstrapper).
9596
$manager = new CacheManager($app);
9697

98+
// When DatabaseTenancyBootstrapper is used, database stores whose 'connection'
99+
// config is null fall back to the default DB connection ('tenant'). Reset each
100+
// such store to its explicitly configured connection, or fall back to central.
101+
$centralConnection = $app['config']['tenancy.database.central_connection'];
102+
$manager->extend('database', function ($_app, array $config) use ($centralConnection) {
103+
$config['connection'] ??= $centralConnection;
104+
105+
/** @var CacheManager $this */
106+
return $this->createDatabaseDriver($config); // @phpstan-ignore method.protected
107+
});
108+
109+
// DatabaseCacheBootstrapper explicitly writes 'tenant' into each store's 'connection'
110+
// config. The extend() closure above would then read 'tenant' as the configured value
111+
// (not null) and use it directly, so the central connection fallback wouldn't be used.
112+
// This callback is used to correct those connections back to central for globalCache.
97113
if (static::$adjustCacheManagerUsing !== null) {
98114
(static::$adjustCacheManagerUsing)($manager);
99115
}

tests/CachedTenantResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
['redis', [CacheTenancyBootstrapper::class]],
166166
['redis', [CacheTagsBootstrapper::class]],
167167
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
168+
['database', [DatabaseTenancyBootstrapper::class, CacheTenancyBootstrapper::class]],
168169
]);
169170

170171
test('cache is invalidated when the tenant is deleted', function (string $resolver, bool $configureTenantModelColumn) {

tests/GlobalCacheTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
['redis', [CacheTagsBootstrapper::class]],
166166
['redis', [CacheTenancyBootstrapper::class]],
167167
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
168+
['database', [DatabaseTenancyBootstrapper::class, CacheTenancyBootstrapper::class]],
168169
])->with([
169170
'helper',
170171
'facade',

0 commit comments

Comments
 (0)