Skip to content

Add cache touch support#68

Merged
TorstenDittmann merged 4 commits into
masterfrom
feat/cache-touch
May 11, 2026
Merged

Add cache touch support#68
TorstenDittmann merged 4 commits into
masterfrom
feat/cache-touch

Conversation

@TorstenDittmann

Copy link
Copy Markdown
Contributor

Summary

  • Add a public touch() cache API for refreshing an existing entry timestamp without changing its data.
  • Implement touch() across all adapters, delegating through existing load/save paths where appropriate.
  • Add shared adapter coverage for successful and missing-entry touch behavior.

Testing

  • composer format
  • composer check
  • composer lint
  • ./vendor/bin/phpunit tests/Cache/MemoryTest.php tests/Cache/NoneTest.php tests/Cache/FilesystemTest.php --filter 'testCacheTouch|testCacheSave|testCachePurge'

Notes

  • Full PHPUnit was not green locally because Redis hostnames are unavailable, the Memcached extension is missing, and the filesystem case-sensitivity assertion fails on this macOS case-insensitive filesystem.

@greptile-apps

greptile-apps Bot commented May 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a touch() method across all cache adapters and the Cache facade, allowing callers to refresh an entry's timestamp without replacing its data. The implementation is complete and well-structured for the simpler adapters (Memory, Filesystem, None, Pool, Sharding).

  • Redis and RedisCluster implement touch() as a hGet → update timehSet sequence; this is non-atomic, so a concurrent save() call on the same key between those two operations will have its payload silently overwritten by the stale data read during touch().
  • Memcached and Hazelcast have the same non-atomic get→set pattern, making them susceptible to the same concurrent-write data corruption.
  • The shared test in Base::testCacheTouch() correctly validates the core TTL-refresh behavior (save, sleep past TTL, confirm expired, touch, confirm accessible) and the missing-key false-return path.

Confidence Score: 4/5

Safe to merge for single-writer workloads; concurrent save+touch on the same key in Redis, RedisCluster, Memcached, or Hazelcast can silently revert a just-saved payload to stale data.

The non-atomic read-modify-write in the network-backed adapters (Redis, RedisCluster, Memcached, Hazelcast) introduces a window where a concurrent save() can be overwritten by touch() with old payload content. The race is unlikely in low-concurrency deployments but is a real correctness concern in high-throughput environments where the same key is updated and touched simultaneously.

src/Cache/Adapter/Redis.php, src/Cache/Adapter/RedisCluster.php, src/Cache/Adapter/Memcached.php, src/Cache/Adapter/Hazelcast.php — all four implement touch() with a non-atomic read-modify-write.

Important Files Changed

Filename Overview
src/Cache/Adapter.php Adds touch(string $key, string $hash = ''): bool to the interface; straightforward and consistent with existing method signatures.
src/Cache/Adapter/Redis.php Adds touch() via non-atomic hGethSet which can corrupt data under concurrent save() calls.
src/Cache/Adapter/RedisCluster.php Adds touch() with the same non-atomic read-modify-write pattern as Redis; also includes a correct defensive is_string guard added to load().
src/Cache/Adapter/Memcached.php Adds touch() via non-atomic get→set; subject to the same concurrent-write race as Redis.
src/Cache/Adapter/Hazelcast.php Adds touch() via non-atomic get→set; subject to the same concurrent-write race as Redis.
src/Cache/Adapter/Memory.php Adds touch() that directly mutates the in-memory store; safe in single-threaded PHP, correctly guards against missing keys.
src/Cache/Adapter/Filesystem.php Adds touch() using PHP's touch() + clearstatcache(); atomic at the filesystem level, consistent with how the adapter ignores $hash in load()/save().
src/Cache/Adapter/None.php Adds a no-op touch() returning false, matching the adapter's overall no-op contract.
src/Cache/Adapter/Pool.php Delegates touch() to the pooled adapter via the existing delegate() mechanism; correct and consistent with all other methods.
src/Cache/Adapter/Sharding.php Delegates touch() to the shard selected by key, consistent with save() and purge().
src/Cache/Cache.php Adds touch() with case-sensitivity handling and telemetry recording, consistent with how load() and purge() are structured.
tests/Cache/Base.php Adds testCacheTouch() that saves, sleeps past TTL, verifies expiry, touches, and confirms the entry is accessible again; covers missing-key case too.
tests/Cache/NoneTest.php Overrides testCacheTouch() to confirm the None adapter always returns false, matching its no-op behavior.

Reviews (3): Last reviewed commit: "fix: stabilize filesystem touch test" | Re-trigger Greptile

Comment thread tests/Cache/Base.php
Comment thread src/Cache/Adapter/Memory.php Outdated
Comment thread src/Cache/Adapter/Redis.php
@TorstenDittmann TorstenDittmann merged commit ef52a04 into master May 11, 2026
6 checks passed
@TorstenDittmann TorstenDittmann deleted the feat/cache-touch branch May 11, 2026 11:02
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