Skip to content

[13.x] Add flushLocks() support to Cache stores#58907

Open
amirhshokri wants to merge 21 commits intolaravel:masterfrom
amirhshokri:13.x-ability-to-flush-cache-locks
Open

[13.x] Add flushLocks() support to Cache stores#58907
amirhshokri wants to merge 21 commits intolaravel:masterfrom
amirhshokri:13.x-ability-to-flush-cache-locks

Conversation

@amirhshokri
Copy link
Contributor

@amirhshokri amirhshokri commented Feb 18, 2026

What this PR does

This PR introduces the ability to flush all locks from cache stores that support it, by adding a FlushableLock interface and exposing flushLocks() through the Repository and Cache facade.

Motivation

There is recurring community demand for this feature (#36927) — particularly in local development scenarios where locks can become orphaned after a crashed request or a failed queue job, forcing developers to either wait for expiry or wipe the entire cache with Cache::flush().

Changes

New interface — Illuminate\Contracts\Cache\FlushableLock:

Stores that support flushing locks implement this interface alongside the existing LockProvider interface. This keeps the design non-breaking — stores that don't support it simply don't implement it.

Implemented on: ArrayStore, FileStore, DatabaseStore, RedisStore

Repository:

Added two methods:

  • flushLocks(): bool — delegates to the store if it implements FlushableLock, otherwise throws a BadMethodCallException consistent with how tags() behaves on non-taggable stores.
  • supportsFlushingLocks(): bool — allows callers to check support before calling flushLocks().

Cache facade:

Added docblock annotations for both new methods:

@method static bool flushLocks()
@method static bool supportsFlushingLocks()

Usage

// Check support first
if (Cache::supportsFlushingLocks()) {
    Cache::flushLocks();
}

// Or directly if you know the store supports it
Cache::store('redis')->flushLocks();
Cache::store('database')->flushLocks();

Design decisions

  • Interface over abstract classRedisStore already extends TaggableStore, so an abstract class approach would conflict with PHP's single inheritance. A standalone interface composes cleanly with the existing hierarchy.
  • instanceof over method_exists — unlike supportsTags() which predates a formal interface, supportsFlushingLocks() uses instanceof FlushableLock since the interface is explicitly defined.
  • Scoped to locks onlyflushLocks() only removes lock keys, leaving regular cache entries untouched.

Add flushLocks() support to Cache stores

What this PR does

This PR introduces the ability to flush all locks from cache stores that support it, by adding a FlushableLock interface and exposing flushLocks() through the Repository and Cache facade.

Motivation

There is recurring community demand for this feature ([link to discussions]) — particularly in local development scenarios where locks can become orphaned after a crashed request or a failed queue job, forcing developers to either wait for expiry or wipe the entire cache with Cache::flush().

Changes

New interface — Illuminate\Contracts\Cache\FlushableLock

Stores that support flushing locks implement this interface alongside the existing LockProvider interface. This keeps the design non-breaking — stores that don't support it simply don't implement it.

Implemented on: ArrayStore, FileStore, DatabaseStore, RedisStore

Repository

Added two methods:

  • flushLocks(): bool — delegates to the store if it implements FlushableLock, otherwise throws a BadMethodCallException consistent with how tags() behaves on non-taggable stores.
  • supportsFlushingLocks(): bool — allows callers to check support before calling flushLocks().

Cache facade

Added docblock annotations for both new methods:

@method static bool flushLocks()
@method static bool supportsFlushingLocks()

Usage

// Check support first
if (Cache::supportsFlushingLocks()) {
    Cache::flushLocks();
}

// Or directly if you know the store supports it
Cache::store('redis')->flushLocks();
Cache::store('database')->flushLocks();

Design decisions

  • Interface over abstract classRedisStore already extends TaggableStore, so an abstract class approach would conflict with PHP's single inheritance. A standalone interface composes cleanly with the existing hierarchy.
  • instanceof over method_exists — unlike supportsTags() which predates a formal interface, supportsFlushingLocks() uses instanceof FlushableLock since the interface is explicitly defined.
  • Scoped to locks onlyflushLocks() only removes lock keys, leaving regular cache entries untouched.

Update

Following Taylor's feedback that flushing locks should only be allowed when the lock store is provably isolated from the cache store, a hasSeparateLockStore(): bool method has been added to the FlushableLock interface. Each store defines what isolation means in its own context:

  • RedisStorelockConnection is a different connection name than connection
  • DatabaseStorelockTable is a different table than table
  • ArrayStore — always true, locks live in $locks, cache in $storage
  • FileStorelockDirectory is explicitly set and differs from directory

flushLocks() on each store now guards against non-isolated stores by throwing a RuntimeException before proceeding.

@amirhshokri amirhshokri marked this pull request as draft February 18, 2026 21:09
@amirhshokri
Copy link
Contributor Author

Test failure is unrelated:

1) Illuminate\Tests\Http\HttpClientTest::testTheTransferStatsAreCustomizable
Illuminate\Http\Client\ConnectionException: cURL error 60: SSL certificate OpenSSL verify result: unable to get local issuer certificate (20) (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://example.com/

@amirhshokri amirhshokri marked this pull request as ready for review February 18, 2026 21:33
@taylorotwell
Copy link
Member

I personally think this should only work if the lock connection / storage location is different than the default location.

@taylorotwell taylorotwell marked this pull request as draft February 18, 2026 22:09
@amirhshokri
Copy link
Contributor Author

Thanks for the review @taylorotwell

Good point — I agree with the isolation requirement. I'll refine the implementation to enforce that.

@amirhshokri
Copy link
Contributor Author

Test failure is unrelated:

1) Illuminate\Tests\Http\HttpClientTest::testTheTransferStatsAreCustomizable
Illuminate\Http\Client\ConnectionException: cURL error 60: SSL certificate OpenSSL verify result: unable to get local issuer certificate (20) (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://example.com/

@amirhshokri amirhshokri marked this pull request as ready for review February 19, 2026 18:01
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.

2 participants

Comments