Skip to content

[4.x] Make globalCache always use the central connection with database cache stores#1462

Merged
stancl merged 12 commits into
masterfrom
fix-cache-invalidation
Jun 28, 2026
Merged

[4.x] Make globalCache always use the central connection with database cache stores#1462
stancl merged 12 commits into
masterfrom
fix-cache-invalidation

Conversation

@lukinovec

@lukinovec lukinovec commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed global cache behavior so database-backed cache stores consistently use the configured central database connection, avoiding tenant-bound “global” cache entries.
  • Tests

    • Updated global-cache scenarios to confirm central-connection usage with database-backed stores.
    • Adjusted tenant-update cache invalidation coverage to match the corrected bootstrapper setup.
  • Documentation

    • Expanded guidance on cache-manager adjustments and clarified expected behavior when mixing tenancy bootstrappers with database-backed caches.
    • Added a warning about potential double-scoping when combining database tenancy and database-backed cache stores.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a central-connection override for database-backed global cache stores and updates related bootstrapper docs and test datasets to use CacheTenancyBootstrapper for database cache-store cases.

Changes

Database Cache Connection Centralization

Layer / File(s) Summary
Global cache database driver override
src/TenancyServiceProvider.php, src/Bootstrappers/DatabaseCacheBootstrapper.php, src/Bootstrappers/CacheTenancyBootstrapper.php
globalCache now extends the database cache driver to default unset store connections to tenancy.database.central_connection; related docblocks describe the interaction with the tenancy bootstrappers.
Database cache-store test datasets
tests/CachedTenantResolverTest.php, tests/GlobalCacheTest.php
The database dataset entries now use CacheTenancyBootstrapper with DatabaseTenancyBootstrapper in the relevant tests.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 I hop where caches and tenants meet,
Central connections keep the path complete.
Docs and tests now wag in tune,
A tidy burrow by afternoon,
One hop for stores, one hop for lore,
And database caches roam no more.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: ensuring globalCache uses the central connection for the database cache store.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-cache-invalidation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lukinovec lukinovec changed the title Add datasets to globalCache/invalidation tests as regression [4.x] Make globalCache always use the central connection Jun 1, 2026
@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.05%. Comparing base (04da9c8) to head (9f344bf).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1462      +/-   ##
============================================
+ Coverage     86.03%   86.05%   +0.02%     
  Complexity     1178     1178              
============================================
  Files           185      185              
  Lines          3436     3441       +5     
============================================
+ Hits           2956     2961       +5     
  Misses          480      480              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/TenancyServiceProvider.php`:
- Around line 127-140: The loop eagerly instantiates every database cache store
by calling $manager->store($name)->getStore(), causing unnecessary connections;
instead, avoid materialization by registering a lazy decorator for each
configured database store using $manager->extend($name, ...), and inside that
extender wrap/return the real store and call setConnection(...) and
setLockConnection(...) on the DatabaseStore using $store['connection'] ??
$centralConnection and $store['lock_connection'] ?? $store['connection'] ??
$centralConnection so the central-connection fallback is applied only when a
store is actually resolved; reference $manager->extend, DatabaseStore,
setConnection, setLockConnection, $store and $centralConnection to locate and
implement the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e78e02ab-9f44-436e-9714-85a7f2f2445b

📥 Commits

Reviewing files that changed from the base of the PR and between da7eb94 and 5e65c67.

📒 Files selected for processing (3)
  • src/TenancyServiceProvider.php
  • tests/CachedTenantResolverTest.php
  • tests/GlobalCacheTest.php

Comment thread src/TenancyServiceProvider.php Outdated
@lukinovec lukinovec marked this pull request as ready for review June 1, 2026 12:58
@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/CachedTenantResolverTest.php`:
- Line 168: The dataset leaves TenancyServiceProvider::$adjustCacheManagerUsing
mutated by the DatabaseTenancyBootstrapper case so the subsequent
CacheTenancyBootstrapper case inherits the callback; update the test fixture in
CachedTenantResolverTest (in the beforeEach/afterEach surrounding the dataset)
to explicitly reset TenancyServiceProvider::$adjustCacheManagerUsing to its
known default (e.g., null or the original default closure) in addition to the
existing Tenant::$extraCustomColumns reset so each case exercises
makeDatabaseCacheStoresCentral() independently.

In `@tests/GlobalCacheTest.php`:
- Line 168: Tests leave TenancyServiceProvider::$adjustCacheManagerUsing set by
DatabaseTenancyBootstrapper, causing stale static state that affects later
CacheTenancyBootstrapper assertions; reset the static between runs by explicitly
assigning the class default (e.g.
TenancyServiceProvider::$adjustCacheManagerUsing = null or the original default)
in the test suite setup/teardown (beforeEach()/afterEach()) in the
GlobalCacheTest so each test starts with a clean
TenancyServiceProvider::$adjustCacheManagerUsing state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 574ac1d9-33d8-4426-852f-0208fe2e5108

📥 Commits

Reviewing files that changed from the base of the PR and between da7eb94 and fd1aa24.

📒 Files selected for processing (3)
  • src/TenancyServiceProvider.php
  • tests/CachedTenantResolverTest.php
  • tests/GlobalCacheTest.php

Comment thread tests/CachedTenantResolverTest.php
Comment thread tests/GlobalCacheTest.php

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes globalCache behavior so that database-backed cache stores use the central (or explicitly configured) DB connection even when the app is currently in tenant context (notably when using DatabaseTenancyBootstrapper + CacheTenancyBootstrapper). This prevents global cache reads/writes and cache invalidation from accidentally targeting tenant databases.

Changes:

  • Adjust globalCache’s freshly-instantiated CacheManager to reset database cache stores onto the central/configured connection.
  • Add regression coverage for the database cache driver when used with CacheTenancyBootstrapper.
  • Reset TenancyServiceProvider::$adjustCacheManagerUsing in tests to avoid cross-test state leakage.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/TenancyServiceProvider.php Ensures database cache stores on the globalCache manager are pointed at the central/configured DB connection.
tests/GlobalCacheTest.php Adds regression dataset for database cache + CacheTenancyBootstrapper and resets the static cache-manager adjuster between tests.
tests/CachedTenantResolverTest.php Adds regression dataset for database cache + CacheTenancyBootstrapper and resets the static cache-manager adjuster between tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@lukinovec lukinovec changed the title [4.x] Make globalCache always use the central connection [4.x] Make globalCache always use the central connection with database cache store Jun 5, 2026
Comment thread tests/GlobalCacheTest.php Outdated
stancl and others added 6 commits June 6, 2026 15:03
Mention that it's not intended to be used with database cache stores (it *can* be used with db cache stores, there's just a little unexpected behavior).
Instead of looping through all database cache stores and setting their connection to central, use extend() to create the database stores lazily, using central connection
PHPStan complains that createDatabaseDriver is a protected method on CacheManager. Since PHPStan doesn't handle $this in the extend()'s closure well, I'm adding an ignore.
Comment on lines +28 to +31
* (typically the GlobalCache facade or global_cache() helper), even though this bootstrapper explicitly
* sets the connection to tenant for all scoped cache stores. Extending database store on the global cache manager
* 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added text is a bit difficult to understand. Is this basically trying to say that the extend() approach we've added in this PR isn't enough to make global cache DB stores work when DatabaseCacheBootstrapperthe old logic using static::$adjustCacheManagerUsing is still needed — because even though the newly added extend() logic does normally make DB stores use the central DB, in this case $config['connection'] ??= $centralConnection; resolves to tenant because this bootstrapper also overrides the connections' config which makes that ??= not do anything and the stores stay tenant?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to rewrite the added text with this understanding in mind. If that's incorrect, let me know here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated all comments in 9f344bf, let me know if they're all correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming that the changes seem good and correct

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/Bootstrappers/DatabaseCacheBootstrapper.php (1)

24-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Simplify and sharpen the docblock explanation.

The current text is technically correct but dense. Future maintainers may struggle to follow the two-layer correction (extend() + callback). Consider condensing to the essential causality:

  * Notably, this bootstrapper sets TenancyServiceProvider::$adjustCacheManagerUsing to a callback
  * that ensures all affected stores still use the central connection when accessed via global cache
- * (typically the GlobalCache facade or global_cache() helper). The code in TenancyServiceProvider
- * that uses `extend()` callbacks to make database stores on the global cache manager use the central
- * connection only corrects stores scoped by the Database*Tenancy*Bootstrapper. This bootstrapper
- * also changes the stores' connection in the *config* to 'tenant' which doesn't let that callback
- * change the connection back to central on the global cache manager.
+ * (typically the GlobalCache facade or global_cache() helper). This is needed because the
+ * `extend()` callback in TenancyServiceProvider only overrides `null` connections; this
+ * bootstrapper writes `'tenant'` into config, which bypasses that null-coalescing fallback.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Bootstrappers/DatabaseCacheBootstrapper.php` around lines 24 - 32, The
docblock in DatabaseCacheBootstrapper is too dense and should be simplified to
focus on the core behavior. Reword the explanation around
TenancyServiceProvider::$adjustCacheManagerUsing and the global
cache/GlobalCache path so it clearly states that this bootstrapper changes the
cache store config to tenant while also overriding the connection back to
central for global cache access. Keep the distinction between the extend()
behavior and this callback, but condense the wording so maintainers can
understand the causality quickly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Bootstrappers/CacheTenancyBootstrapper.php`:
- Around line 21-25: The warning text in CacheTenancyBootstrapper should be made
more precise, since “harmless” understates the operational complexity of double
scoping. Update the docblock near the database cache store note to describe that
the cache will be scoped both by tenant database and by prefix, making
inspection and eviction harder, while avoiding language that implies it is
simply harmless.

In `@src/TenancyServiceProvider.php`:
- Around line 109-112: Update the explanatory comment near the cache store
extension logic in TenancyServiceProvider so it accurately describes what the
extend() callback does: it restores the central connection for any database
cache store whose connection config is null, not just stores affected by
DatabaseTenancyBootstrapper. Make the wording reflect that
DatabaseCacheBootstrapper is the one that writes 'tenant' into the store config
and bypasses the null fallback, and keep the explanation tied to the globalCache
handling in the extend() closure.

---

Duplicate comments:
In `@src/Bootstrappers/DatabaseCacheBootstrapper.php`:
- Around line 24-32: The docblock in DatabaseCacheBootstrapper is too dense and
should be simplified to focus on the core behavior. Reword the explanation
around TenancyServiceProvider::$adjustCacheManagerUsing and the global
cache/GlobalCache path so it clearly states that this bootstrapper changes the
cache store config to tenant while also overriding the connection back to
central for global cache access. Keep the distinction between the extend()
behavior and this callback, but condense the wording so maintainers can
understand the causality quickly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e8b27373-c1c3-4254-b6fe-1ffbbc2af1c1

📥 Commits

Reviewing files that changed from the base of the PR and between ff8f73e and 9f344bf.

📒 Files selected for processing (3)
  • src/Bootstrappers/CacheTenancyBootstrapper.php
  • src/Bootstrappers/DatabaseCacheBootstrapper.php
  • src/TenancyServiceProvider.php

Comment thread src/Bootstrappers/CacheTenancyBootstrapper.php
Comment thread src/TenancyServiceProvider.php
@stancl stancl merged commit ecf0312 into master Jun 28, 2026
14 checks passed
@stancl stancl deleted the fix-cache-invalidation branch June 28, 2026 01:30
@stancl stancl changed the title [4.x] Make globalCache always use the central connection with database cache store [4.x] Make globalCache always use the central connection with database cache stores Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants