Skip to content

feat(cache): harden lifecycle safety and complete Laravel parity - #455

Merged
binaryfire merged 22 commits into
0.4from
audit/cache-lifecycle-audit
Jul 26, 2026
Merged

feat(cache): harden lifecycle safety and complete Laravel parity#455
binaryfire merged 22 commits into
0.4from
audit/cache-lifecycle-audit

Conversation

@binaryfire

@binaryfire binaryfire commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

This brings the Cache package up to the current supported Laravel surface while tightening the parts that need different ownership rules in a long-lived, pooled Swoole runtime.

The user-facing additions include the Storage cache driver, rememberWithWarmth(), closure-based TTLs, incomplete-class handling, current facade metadata, and the corresponding configuration and documentation. The port keeps Hypervel's existing nullable values, Swoole store, failover and stack stores, Redis tag modes, transforms, SafeScan behavior, and explicit pinned-connection API.

The rest of the change fixes lifecycle and correctness issues that only become expensive or unsafe once a worker serves many requests:

  • Cache locks, limiters, failover stores, and stack stores now perform exhaustive cleanup while preserving the earliest failure.
  • File-backed stores write fixed-width expiration headers, so large timestamps cannot corrupt the stored payload boundary.
  • Custom-amount rate limiters install their expiry correctly.
  • Swoole-backed permanent values and framework metadata remain permanent instead of expiring after a synthetic one-year TTL.
  • Swoole lock refresh and timer startup are ownership-safe and transactional.
  • Tagged cache operations publish the expected event sequence with the correct store name, tags, keys, values, and failure details.
  • Flexible-cache marker and lock identities include the resolved tagged namespace, preventing unrelated tagged caches from sharing coordination state.

Redis-backed cache work no longer holds a pooled connection across arbitrary application callbacks. Hits still use one checkout. Misses use short read and write checkouts around the callback rather than retaining a lease while user code runs, and no additional Redis command is introduced. Explicit withPinnedConnection() remains available for callers that intentionally need one held connection.

Redis tag maintenance now follows PhpRedis SafeScan cursor semantics, continues through sparse pages, borrows connections per page or chunk, avoids nested checkouts, and does not retain complete tag result sets. Permanent any-mode additions use the existing atomic paths without inventing a new event contract.

The object serialization boundary is explicit and secure by default. Cache declares the allowed serializable classes, applies that policy to Swoole user payloads without restricting framework-owned metadata, and documents the native PhpRedis serializer boundary: extension-level serializers deserialize before Cache can enforce its own class policy.

Finally, this removes the superseded Redis remember-operation hierarchy, unused operation containers and exception types, repeated factory resolution, and drift-prone call-site configuration defaults. An exhaustive regression now warms every lazy RedisStore cache and verifies that each boot-time configuration mutator invalidates the complete set, so a future cache slot cannot silently retain stale derived state.

Why

The previous implementation mixed Laravel's request-lifetime assumptions with pooled connections and worker-lifetime state. That created several failure modes:

  • a Redis lease could remain checked out while an application callback performed unrelated or nested work;
  • cleanup failures could replace the application failure that triggered cleanup;
  • partial cleanup could strand locks or leave stack layers inconsistent;
  • synthetic expirations could make supposedly permanent Swoole values disappear;
  • Redis scans could terminate early, perform redundant work, or retain a connection and an unbounded result set;
  • tagged repositories could coordinate through identities that did not include their resolved namespace;
  • custom tagged operations emitted incomplete or misleading cache events.

These fixes move each responsibility to the layer that owns it. They do not add a retry loop, registry, state machine, compatibility path, or hot-path synchronization.

Compatibility

Current supported Laravel Cache APIs, facade metadata, configuration behavior, and documentation are preserved. Hypervel-specific behavior remains where the runtime requires it:

  • pooled Redis connections have explicit lease ownership;
  • Swoole and Redis stores retain their existing advanced capabilities;
  • the nullable cache.limiter key is declared explicitly through CACHE_LIMITER;
  • atomic any-mode add() remains event-free, matching its established atomic contract.

The secure serialization default may require applications that intentionally cache objects to opt those classes in. The documentation shows that configuration and calls out the separate native PhpRedis serializer boundary.

Performance

The hot-path changes are bounded:

  • Redis hits keep one connection checkout.
  • Redis misses replace one long-held checkout with two short checkouts and issue no extra Redis command.
  • Redis tag scans reduce lease duration and retained memory by borrowing per page or chunk.
  • Redis tag operations reuse the factory already owned by RedisStore instead of repeatedly resolving it.
  • Optional tagged events add only the expected listener check and event construction beside existing Redis, Lua, or stack I/O.
  • Fixed-width expiration formatting is negligible beside filesystem I/O.

The change removes traffic-proportional connection retention without adding locks, polling, retries, or unbounded worker state.

Testing

Validation covers:

  • the complete Cache unit surface;
  • Redis and Valkey integration behavior, including pool ownership, scans, tag modes, and permanent additions;
  • Storage and Swoole serialization, expiration, timer, and lock behavior;
  • lock, limiter, failover, and stack failure precedence;
  • Cache events and tagged repository identities;
  • Auth and Sanctum object-cache consumers;
  • Testbench configuration inheritance;
  • exhaustive RedisStore lazy-cache invalidation;
  • package manifests, stale references, formatting, static analysis, the full parallel framework suite, and Testbench package and dogfood suites.

Summary by CodeRabbit

  • New Features

    • Added a filesystem-backed cache driver with expiration, storage, and maintenance support.
    • Added configurable serialization allowlists and handling for unavailable cached classes.
    • Added warm-cache status reporting for cache lookups.
    • Added lock-flushing support across supported cache stores.
    • Cache tags now support permanent entries when no expiration is specified.
  • Bug Fixes

    • Improved Redis tag scanning, pruning, event reporting, and connection handling.
    • Preserved original callback errors when lock release also fails.
    • Improved cache layering, failover, timer rollback, and custom rate-limit increments.
  • Documentation

    • Expanded guidance for serialization security, Redis configuration, cache drivers, storage, tagging, and authentication token caching.

Format cache expiration timestamps into the ten-byte header reserved by the file-store format, including dates beyond the current Unix timestamp width.

Cover put, add, and refresh behavior so payload offsets remain stable and long-lived entries cannot corrupt their serialized values.
Add the current Laravel-compatible filesystem-disk cache store with checked reads and writes, fixed-width expiration metadata, pruning, and path-prefix support.

Exercise successful operations and native filesystem failure contracts through a reusable in-memory filesystem fixture.
Register the storage driver, declare the nullable limiter and secure serialization settings, and pass serialization policy into Swoole stores.

Replace ambiguous container array access and dead call-site defaults with canonical typed resolution while preserving manager aliases, test spies, and the framework's merged configuration behavior.
Add closure TTLs, rememberWithWarmth, and incomplete-class handling across the repository contract, implementation, manager-facing facade metadata, and test cleanup.

Keep nullable values and raw-readable wrappers intact, namespace flexible-cache ownership by the resolved item key, and release Redis-backed reads before user callbacks execute.
Make callback-scoped locks attempt release on every acquired path without allowing a release error to replace the callback's original failure.

Cover successful callbacks, callback failures, release failures, and their combined precedence so lock ownership remains deterministic.
Apply exhaustive release and earliest-failure precedence to both concurrency-limiter callback APIs after a lease has been acquired.

Retain existing false-result behavior and verify the callback/release failure matrix without adding logging, retry, or lifecycle machinery.
Compare a newly created counter with the requested increment amount rather than the hard-coded value one before installing its decay window.

Add regression coverage for custom increments so their first write cannot accidentally remain permanent.
Require every failover backend to support lock cleanup before starting, then flush all stores exhaustively while preserving false results and the earliest exception.

Report separate-lock cleanup consistently and cover unsupported capabilities, partial failures, and successful cleanup across every configured store.
Expose truthful lock-flush behavior from memoized stores when their backing store supports it and make the null store a real no-op lock provider.

Cover capability propagation and cleanup results so failover preflight cannot accept vacuous or incomplete implementations.
Compensate completed stack writes in reverse order and continue forced forget and flush operations after false results or exceptions.

Preserve the earliest failure, aggregate unsuccessful cleanup truthfully, and retain blocked object payloads during cache promotion.
Represent only forever values and permanent framework metadata with pseudo-permanent Swoole expirations while retaining real TTLs and verifying permanent lock ownership.

Apply the configured unserialization policy to public Swoole cache values without restricting counters or internal metadata, with integration coverage for cached authentication models.
Retain every native timer ID, roll back earlier registrations when later startup fails, and clear the exact owned timers during teardown.

Exercise partial creation, callback failure reporting, and deterministic cleanup without introducing a separate registry or retry path.
Emit the established repository event sequence for custom all-mode and any-mode reads, writes, failures, deletes, and flushes with truthful store names and tags.

Keep atomic any-mode additions event-free, preserve plain-key behavior, and ensure incomplete cached objects are handled once at the owning read boundary.
Remove the six hidden remember operation implementations that held one pooled Redis connection across arbitrary user callbacks and child-coroutine work.

Use ordinary short read and write checkouts on misses, retain explicit withPinnedConnection for callers that intentionally need one lease, and delete the obsolete operation caches, exception, tests, and constructor dependency.
Inject the Redis factory already owned by RedisStore and borrow connections only for each scan page or deletion chunk instead of retaining a lease across lazy iteration.

Follow PhpRedis SafeScan cursor semantics through empty nonterminal pages, avoid redundant terminal requests, bound per-tag deduplication, and remove lease-held sleeps from pruning.
Teach both clustered and Lua-backed any-mode add paths to represent a null TTL as truly permanent rather than assigning an arbitrary one-year expiration.

Keep positive TTL behavior atomic, align permanent tag metadata with the shared maximum-expiry contract, and verify the result through unit and real Redis coverage.
Read required cache and Redis command configuration from the merged framework config instead of repeating fallback values at each call site.

Retain nullable detection where absence is meaningful and cover the doctor command's declared configuration contract.
Document the storage driver, closure TTLs, remember warmth reporting, incomplete-class handling, serialization policy, rate-limiter store selection, and tagged event behavior.

Explain the supported Swoole, failover, stack, and Redis-specific boundaries in the same task-first style as the surrounding framework documentation.
Cross-reference the cache serialization allowlist from authentication and Sanctum guidance so cached framework models remain an explicit application choice.

Clarify that native PhpRedis serializers deserialize before Cache can enforce its class policy, allowing applications to configure the relevant connection boundary deliberately.
Exercise every lazy RedisStore cache through its real warming accessor and verify that each boot-time configuration mutator clears the complete cache set.

Derive the authoritative set from nullable private cache properties so newly introduced slots fail loudly until their invalidation behavior is covered. Keep setLockConnection outside the matrix because lock connections are resolved per call and are never captured by a cached instance.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@binaryfire, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 970ade1b-5efa-4fb8-a1b0-65cf4f4497da

📥 Commits

Reviewing files that changed from the base of the PR and between 11bb30f and 7bb972b.

📒 Files selected for processing (3)
  • docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
  • docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
  • src/boost/README.md
📝 Walkthrough

Walkthrough

This PR adds filesystem-backed caching, serializable-class controls, warm-cache retrieval, lock-flush support, Redis tagged-cache updates, improved scan and timer cleanup behavior, revised Redis dependency injection, and extensive unit, integration, and documentation coverage.

Changes

Cache contracts and storage

Layer / File(s) Summary
Configuration and public cache APIs
src/foundation/config/cache.php, src/contracts/src/Cache/Repository.php, src/support/src/Facades/Cache.php
Adds the storage, limiter, and serializable_classes settings, plus rememberWithWarmth and unserializable-class handler APIs.
Repository and storage behavior
src/cache/src/Repository.php, src/cache/src/StorageStore.php, src/cache/src/SwooleStore.php, src/cache/src/FileStore.php
Adds serialized filesystem caching, allowlisted unserialization, incomplete-class callbacks, warm-cache reads, fixed expiration headers, and permanent Swoole entries.
Lock and layered-store behavior
src/cache/src/FailoverStore.php, src/cache/src/MemoizedStore.php, src/cache/src/NullStore.php, src/cache/src/StackStore.php, src/cache/src/Lock.php
Adds lock-flushing capabilities, preserves callback exceptions when release fails, and separates rollback-on-failure from all-layer operations.
Cache manager and timer wiring
src/cache/src/CacheManager.php, src/cache/src/Listeners/CreateSwooleTimers.php, src/cache/src/SwooleTimer.php
Adds storage-driver construction, container-based resolution, serialization callback forwarding, and timer registration rollback.
Validation coverage
tests/Cache/*, tests/Integration/*
Covers storage persistence, serializable classes, lock flushing, exception ordering, Swoole permanence, and timer cleanup.

Redis tagged caching

Layer / File(s) Summary
Redis store dependency wiring
src/cache/src/RedisStore.php, src/cache/src/Redis/Support/StoreContext.php, tests/Cache/Redis/*
Removes pool-factory construction paths and injects Redis factories into store contexts and tests.
Tagged writes and events
src/cache/src/AnyModeTaggedCache.php, src/cache/src/StackTaggedCache.php, src/cache/src/Redis/AllTaggedCache.php, src/cache/src/Redis/AnyTaggedCache.php
Adds plain-key event helpers, write-success/failure events, store names in payloads, and removes tagged remember implementations.
Redis TTL and scan behavior
src/cache/src/Redis/Operations/*, tests/Cache/Redis/Operations/*
Adds permanent any-tag add handling, standardizes scan cursors, continues after empty pages, deduplicates all-tag entries, and releases connections per deletion chunk.
Redis integration validation
tests/Integration/Cache/Redis/*
Validates connection release before callbacks, all-mode tag flushing, serialization settings, and updated cluster context construction.

Documentation

Layer / File(s) Summary
Cache serialization and driver documentation
src/boost/docs/cache.md, src/boost/docs/redis.md
Documents serializable-class allowlists, Redis serializer configuration, storage drivers, warmth results, tag support, and cache events.
Authentication model caching guidance
src/auth/README.md, src/boost/docs/authentication.md, src/boost/docs/sanctum.md
Documents allowlisting cached authentication, token, tokenable, and personal-access-token model classes.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the PR’s main themes: cache lifecycle hardening and broader Laravel parity work.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/cache-lifecycle-audit

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.

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR substantially expands and hardens the Cache package.

  • Adds Storage-backed caching, closure-based TTLs, warm-cache status reporting, serialization allowlists, and current facade/configuration support.
  • Revises Redis connection ownership, tagged-cache operations, scans, events, and namespace coordination.
  • Strengthens cleanup and failure precedence across locks, limiters, failover stores, stack stores, Swoole timers, and permanent values.
  • Adds broad unit and integration coverage and updates cache, Redis, authentication, and Sanctum documentation.

Confidence Score: 4/5

The PR appears safe to merge, with the previously reported nested incomplete-class diagnostic limitation still outstanding.

The registered unserializable-class callback is still reached only when the cache value itself is an incomplete class; nested incomplete classes remain undetected, but no blocking failure remains.

Files Needing Attention: src/cache/src/Repository.php

Important Files Changed

Filename Overview
src/cache/src/Repository.php Adds warm-cache reporting, closure TTL support, incomplete-class handling, and namespace-aware flexible-cache coordination.
src/cache/src/StorageStore.php Introduces the filesystem-abstraction-backed cache store and its serialization and expiration behavior.
src/cache/src/RedisStore.php Refactors Redis checkout ownership and invalidation of lazily derived store configuration.
src/cache/src/SwooleStore.php Updates serialization boundaries and preserves truly permanent Swoole-backed values.
src/cache/src/StackStore.php Makes layered-store operations and cleanup exhaustive while retaining the earliest failure.
src/cache/src/FailoverStore.php Revises failover execution and cleanup behavior to preserve primary failures.
src/cache/src/Redis/AllTaggedCache.php Updates all-mode tagged-cache operations, event behavior, and Redis resource ownership.
src/cache/src/Redis/AnyTaggedCache.php Updates any-mode tagged-cache operations, permanent additions, and event behavior.
src/cache/src/RateLimiter.php Corrects custom-amount limiter expiration installation.
src/cache/src/SwooleTimer.php Makes timer startup transactional and failure-safe.

Reviews (2): Last reviewed commit: "docs(audit): complete cache lifecycle wo..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/cache/src/Redis/AllTaggedCache.php (1)

51-76: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Event emission is now asymmetric between add()'s two branches.

The null-TTL path delegates to forever() and therefore emits WritingKey + KeyWritten/KeyWriteFailed, while the TTL path (Lines 62-67) still writes without emitting anything. Listeners will see writes for some add() calls only. Consider emitting the same events around the TTL branch for parity.

🤖 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/cache/src/Redis/AllTaggedCache.php` around lines 51 - 76, Update
AllTaggedCache::add so the TTL branch emits the same WritingKey and
KeyWritten/KeyWriteFailed events as the null-TTL branch delegated through
forever(). Wrap the allTagOps()->add()->execute call with the existing cache
event flow, preserving its current key, value, TTL, tags, and boolean result
behavior.
tests/Cache/Redis/AllTaggedCacheTest.php (1)

313-342: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Hardcoded null scan cursor is inconsistent with this file's own convention.

Both mocks assert zScan(..., null, '*', 1000), but this same file's testFlushDispatchesTheRepositoryEventsWithStoreNameAndTags (line 1283) correctly uses PhpRedis::initialScanCursor() for the identical assertion. PhpRedis::initialScanCursor() returns null for phpredis versions >= 6.1.0 and 0 otherwise. Hardcoding null defeats that abstraction and will fail these two tests on any phpredis install where the initial cursor is 0.

🔧 Proposed fix
+use Hypervel\Redis\PhpRedis;
...
         $connection->shouldReceive('zScan')
             ->once()
-            ->with('prefix:_all:tag:people:entries', null, '*', 1000)
+            ->with('prefix:_all:tag:people:entries', PhpRedis::initialScanCursor(), '*', 1000)
             ->andReturnUsing(function ($key, &$cursor) {

(apply to both testFlush and testClearFlushesTaggedItems; PhpRedis is already imported at line 22)

Also applies to: 347-372

🤖 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 `@tests/Cache/Redis/AllTaggedCacheTest.php` around lines 313 - 342, Update the
zScan expectations in testFlush and testClearFlushesTaggedItems to use
PhpRedis::initialScanCursor() instead of hardcoded null, matching the file’s
existing convention while preserving the remaining arguments and assertions.
🧹 Nitpick comments (3)
src/cache/src/Redis/Operations/AnyTag/Forever.php (1)

195-195: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: pass StoreContext::MAX_EXPIRY as an ARGV instead of duplicating the literal in three Lua scripts. The comments now document the coupling, but the value is still hardcoded in three places and a change to the constant would silently diverge.

  • src/cache/src/Redis/Operations/AnyTag/Forever.php#L195-L195: append StoreContext::MAX_EXPIRY to $args in executeUsingLua() and read it as an ARGV in storeForeverWithTagsScript().
  • src/cache/src/Redis/Operations/AnyTag/Decrement.php#L175-L175: same change in executeUsingLua() / decrementWithTagsScript() (note the tag ARGV offset shifts).
  • src/cache/src/Redis/Operations/AnyTag/Increment.php#L175-L175: same change in executeUsingLua() / incrementWithTagsScript().
🤖 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/cache/src/Redis/Operations/AnyTag/Forever.php` at line 195, Pass
StoreContext::MAX_EXPIRY through the Lua arguments instead of duplicating the
literal: update executeUsingLua() and storeForeverWithTagsScript() in
src/cache/src/Redis/Operations/AnyTag/Forever.php#L195-L195, executeUsingLua()
and decrementWithTagsScript() in
src/cache/src/Redis/Operations/AnyTag/Decrement.php#L175-L175 (adjust tag ARGV
offsets), and executeUsingLua() and incrementWithTagsScript() in
src/cache/src/Redis/Operations/AnyTag/Increment.php#L175-L175 to read the
appended expiry value from ARGV.
src/cache/src/Limiters/ConcurrencyLimiter.php (1)

84-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate release-preserving-exception logic across two files.

Both ConcurrencyLimiter::block() and ConcurrencyLimiterBuilder::then() implement the identical "preserve callback failure, swallow release failure" try/catch/rethrow pattern independently. Extracting a shared helper (e.g. a small trait method or static helper taking the Lease/Lock and callback) would keep both call sites in sync if this semantics ever needs adjustment (e.g., adding logging for the swallowed release exception).

  • src/cache/src/Limiters/ConcurrencyLimiter.php#L84-L107: extract the try/catch/rethrow block from block() into a shared helper.
  • src/cache/src/Limiters/ConcurrencyLimiterBuilder.php#L109-L136: reuse the same helper from then() instead of duplicating the block.
🤖 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/cache/src/Limiters/ConcurrencyLimiter.php` around lines 84 - 107, Extract
the shared callback-execution and lease/lock-release exception handling from
ConcurrencyLimiter::block() into a reusable helper, then update
ConcurrencyLimiterBuilder::then() to call that helper instead of duplicating the
try/catch/rethrow logic. Apply the shared change at
src/cache/src/Limiters/ConcurrencyLimiter.php lines 84-107 and reuse it at
src/cache/src/Limiters/ConcurrencyLimiterBuilder.php lines 109-136, preserving
callback failures as primary while swallowing release failures.
src/cache/src/StackTaggedCache.php (1)

51-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated write+event pattern from put()/forever().

Both methods repeat the same shape: emit WritingKey, perform the tagged write, then branch into KeyWritten/KeyWriteFailed with nearly identical event construction. A small protected helper (e.g. writeTaggedRecord(string $key, array $record, mixed $value, ?int $seconds = null): bool) that performs the write and emits the three events would remove the duplication and keep both call sites in sync if the event contract changes again.

♻️ Sketch of the extraction
+    protected function writeTaggedRecord(string $key, array $record, mixed $value, ?int $seconds = null): bool
+    {
+        $this->event(WritingKey::class, fn (): WritingKey => new WritingKey($this->getName(), $key, NullSentinel::unwrap($value), $seconds));
+
+        $result = $this->store->putRecordTagged($this->tags->getNames(), $key, $record);
+
+        $this->event(
+            $result ? KeyWritten::class : KeyWriteFailed::class,
+            fn () => $result
+                ? new KeyWritten($this->getName(), $key, NullSentinel::unwrap($value), $seconds)
+                : new KeyWriteFailed($this->getName(), $key, NullSentinel::unwrap($value), $seconds)
+        );
+
+        return $result;
+    }

Also applies to: 97-106, 111-136

🤖 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/cache/src/StackTaggedCache.php` around lines 51 - 92, Extract the
duplicated tagged-write and event flow from put() and forever() into a protected
helper such as writeTaggedRecord(). Have it emit WritingKey, call
putRecordTagged(), then emit KeyWritten or KeyWriteFailed and return the result;
update both methods to delegate to this helper while preserving their existing
TTL and record behavior.
🤖 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/cache/src/Redis/Operations/AllTag/GetEntries.php`:
- Around line 31-34: Move the $seen initialization outside the foreach ($tagIds
as $tagId) loop in the all-mode entry retrieval flow, while retaining it across
all tag scans. Ensure duplicate keys shared by multiple tag hashes are yielded
only once, without changing the existing per-tag cursor scanning behavior.

In `@src/cache/src/StorageStore.php`:
- Around line 45-55: Update StorageStore::__construct to reject an empty or
whitespace-only $directory after trimming it, throwing an appropriate framework
or PHP exception before assigning the normalized path. Preserve valid normalized
directory values and ensure the related flush() path cannot operate with an
empty storage directory.
- Around line 79-85: Update StorageStore::add to use an atomic backend
conditional-create primitive instead of calling get() before put(). Ensure the
operation succeeds only when the key does not already exist, including when its
stored value is null, and preserves the existing TTL and boolean contract across
local and remote-disk backends.

---

Outside diff comments:
In `@src/cache/src/Redis/AllTaggedCache.php`:
- Around line 51-76: Update AllTaggedCache::add so the TTL branch emits the same
WritingKey and KeyWritten/KeyWriteFailed events as the null-TTL branch delegated
through forever(). Wrap the allTagOps()->add()->execute call with the existing
cache event flow, preserving its current key, value, TTL, tags, and boolean
result behavior.

In `@tests/Cache/Redis/AllTaggedCacheTest.php`:
- Around line 313-342: Update the zScan expectations in testFlush and
testClearFlushesTaggedItems to use PhpRedis::initialScanCursor() instead of
hardcoded null, matching the file’s existing convention while preserving the
remaining arguments and assertions.

---

Nitpick comments:
In `@src/cache/src/Limiters/ConcurrencyLimiter.php`:
- Around line 84-107: Extract the shared callback-execution and
lease/lock-release exception handling from ConcurrencyLimiter::block() into a
reusable helper, then update ConcurrencyLimiterBuilder::then() to call that
helper instead of duplicating the try/catch/rethrow logic. Apply the shared
change at src/cache/src/Limiters/ConcurrencyLimiter.php lines 84-107 and reuse
it at src/cache/src/Limiters/ConcurrencyLimiterBuilder.php lines 109-136,
preserving callback failures as primary while swallowing release failures.

In `@src/cache/src/Redis/Operations/AnyTag/Forever.php`:
- Line 195: Pass StoreContext::MAX_EXPIRY through the Lua arguments instead of
duplicating the literal: update executeUsingLua() and
storeForeverWithTagsScript() in
src/cache/src/Redis/Operations/AnyTag/Forever.php#L195-L195, executeUsingLua()
and decrementWithTagsScript() in
src/cache/src/Redis/Operations/AnyTag/Decrement.php#L175-L175 (adjust tag ARGV
offsets), and executeUsingLua() and incrementWithTagsScript() in
src/cache/src/Redis/Operations/AnyTag/Increment.php#L175-L175 to read the
appended expiry value from ARGV.

In `@src/cache/src/StackTaggedCache.php`:
- Around line 51-92: Extract the duplicated tagged-write and event flow from
put() and forever() into a protected helper such as writeTaggedRecord(). Have it
emit WritingKey, call putRecordTagged(), then emit KeyWritten or KeyWriteFailed
and return the result; update both methods to delegate to this helper while
preserving their existing TTL and record behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 37ddaafa-0c97-4796-93ce-e49d57354cf9

📥 Commits

Reviewing files that changed from the base of the PR and between 2108264 and 11bb30f.

📒 Files selected for processing (99)
  • src/auth/README.md
  • src/boost/docs/authentication.md
  • src/boost/docs/cache.md
  • src/boost/docs/redis.md
  • src/boost/docs/sanctum.md
  • src/cache/src/AnyModeTaggedCache.php
  • src/cache/src/CacheManager.php
  • src/cache/src/CacheServiceProvider.php
  • src/cache/src/FailoverStore.php
  • src/cache/src/FileStore.php
  • src/cache/src/Limiters/ConcurrencyLimiter.php
  • src/cache/src/Limiters/ConcurrencyLimiterBuilder.php
  • src/cache/src/Listeners/BaseListener.php
  • src/cache/src/Listeners/CreateSwooleTimers.php
  • src/cache/src/Lock.php
  • src/cache/src/MemoizedStore.php
  • src/cache/src/NullStore.php
  • src/cache/src/RateLimiter.php
  • src/cache/src/Redis/AllTaggedCache.php
  • src/cache/src/Redis/AnyTaggedCache.php
  • src/cache/src/Redis/Console/BenchmarkCommand.php
  • src/cache/src/Redis/Console/Concerns/DetectsRedisStore.php
  • src/cache/src/Redis/Console/DoctorCommand.php
  • src/cache/src/Redis/Exceptions/RedisCacheException.php
  • src/cache/src/Redis/Operations/AllTag/Flush.php
  • src/cache/src/Redis/Operations/AllTag/GetEntries.php
  • src/cache/src/Redis/Operations/AllTag/Prune.php
  • src/cache/src/Redis/Operations/AllTag/Remember.php
  • src/cache/src/Redis/Operations/AllTag/RememberForever.php
  • src/cache/src/Redis/Operations/AllTagOperations.php
  • src/cache/src/Redis/Operations/AnyTag/Add.php
  • src/cache/src/Redis/Operations/AnyTag/Decrement.php
  • src/cache/src/Redis/Operations/AnyTag/Forever.php
  • src/cache/src/Redis/Operations/AnyTag/GetTaggedKeys.php
  • src/cache/src/Redis/Operations/AnyTag/Increment.php
  • src/cache/src/Redis/Operations/AnyTag/Prune.php
  • src/cache/src/Redis/Operations/AnyTag/Remember.php
  • src/cache/src/Redis/Operations/AnyTag/RememberForever.php
  • src/cache/src/Redis/Operations/AnyTagOperations.php
  • src/cache/src/Redis/Operations/Remember.php
  • src/cache/src/Redis/Operations/RememberForever.php
  • src/cache/src/Redis/Support/StoreContext.php
  • src/cache/src/RedisStore.php
  • src/cache/src/Repository.php
  • src/cache/src/StackStore.php
  • src/cache/src/StackTaggedCache.php
  • src/cache/src/StorageStore.php
  • src/cache/src/SwooleLock.php
  • src/cache/src/SwooleStore.php
  • src/cache/src/SwooleTimer.php
  • src/contracts/src/Cache/Repository.php
  • src/foundation/config/cache.php
  • src/support/src/Facades/Cache.php
  • tests/Cache/CacheFailoverStoreTest.php
  • tests/Cache/CacheFileStoreTest.php
  • tests/Cache/CacheLockTest.php
  • tests/Cache/CacheManagerTest.php
  • tests/Cache/CacheMemoizedStoreTest.php
  • tests/Cache/CacheNullStoreTest.php
  • tests/Cache/CacheRateLimiterTest.php
  • tests/Cache/CacheRedisStoreTest.php
  • tests/Cache/CacheRepositoryTest.php
  • tests/Cache/CacheSpyMemoTest.php
  • tests/Cache/CacheStackStoreTagsTest.php
  • tests/Cache/CacheStackStoreTest.php
  • tests/Cache/CacheStorageStoreTest.php
  • tests/Cache/CacheSwooleStoreIntervalTest.php
  • tests/Cache/CacheSwooleStoreTest.php
  • tests/Cache/ConcurrencyLimiterTest.php
  • tests/Cache/CreateSwooleTimersTest.php
  • tests/Cache/Fixtures/ArrayFilesystem.php
  • tests/Cache/Redis/AllTaggedCacheTest.php
  • tests/Cache/Redis/AnyTaggedCacheTest.php
  • tests/Cache/Redis/Console/DoctorCommandTest.php
  • tests/Cache/Redis/Operations/AllTag/FlushTest.php
  • tests/Cache/Redis/Operations/AllTag/GetEntriesTest.php
  • tests/Cache/Redis/Operations/AllTag/PruneTest.php
  • tests/Cache/Redis/Operations/AllTag/RememberForeverTest.php
  • tests/Cache/Redis/Operations/AllTag/RememberTest.php
  • tests/Cache/Redis/Operations/AllTagOperationsTest.php
  • tests/Cache/Redis/Operations/AnyTag/AddTest.php
  • tests/Cache/Redis/Operations/AnyTag/DecrementTest.php
  • tests/Cache/Redis/Operations/AnyTag/ForeverTest.php
  • tests/Cache/Redis/Operations/AnyTag/GetTaggedKeysTest.php
  • tests/Cache/Redis/Operations/AnyTag/IncrementTest.php
  • tests/Cache/Redis/Operations/AnyTag/PruneTest.php
  • tests/Cache/Redis/Operations/AnyTag/RememberForeverTest.php
  • tests/Cache/Redis/Operations/AnyTag/RememberTest.php
  • tests/Cache/Redis/Operations/AnyTagOperationsTest.php
  • tests/Cache/Redis/Operations/RememberForeverTest.php
  • tests/Cache/Redis/Operations/RememberTest.php
  • tests/Cache/Redis/RedisCacheTestCase.php
  • tests/Cache/Redis/RedisStoreTest.php
  • tests/Cache/Redis/Support/StoreContextTest.php
  • tests/Integration/Auth/EloquentUserProviderCacheTagsTest.php
  • tests/Integration/Cache/Redis/BasicOperationsIntegrationTest.php
  • tests/Integration/Cache/Redis/ClusterFallbackIntegrationTest.php
  • tests/Integration/Cache/Redis/ConnectionPinningIntegrationTest.php
  • tests/Testbench/DefaultConfigurationTest.php
💤 Files with no reviewable changes (15)
  • src/cache/src/Redis/Operations/RememberForever.php
  • src/cache/src/Redis/Operations/AllTag/RememberForever.php
  • src/cache/src/Redis/Operations/AllTag/Remember.php
  • src/cache/src/Redis/Operations/AnyTag/Remember.php
  • src/cache/src/Redis/Exceptions/RedisCacheException.php
  • tests/Cache/Redis/Operations/RememberTest.php
  • src/cache/src/Redis/Operations/Remember.php
  • tests/Cache/Redis/Operations/RememberForeverTest.php
  • src/cache/src/Redis/Operations/AnyTag/RememberForever.php
  • tests/Cache/Redis/Operations/AnyTag/RememberTest.php
  • tests/Cache/Redis/Operations/AllTag/RememberTest.php
  • src/cache/src/Redis/Operations/AllTagOperations.php
  • tests/Cache/Redis/Operations/AllTag/RememberForeverTest.php
  • tests/Cache/Redis/Operations/AnyTag/RememberForeverTest.php
  • src/cache/src/Redis/Operations/AnyTagOperations.php

Comment thread src/cache/src/Redis/Operations/AllTag/GetEntries.php
Comment thread src/cache/src/StorageStore.php
Comment thread src/cache/src/StorageStore.php
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptile-apps I traced the nested __PHP_Incomplete_Class case through this implementation, current Laravel, and the originating upstream change. The hook intentionally receives a cached value when that returned value itself is incomplete; upstream does not recursively inspect values nested inside arrays or arbitrary object graphs.

Expanding this would create a new contract rather than fix a missed branch. It would require traversal rules for arrays, object visibility, cycles, and potentially user-defined iterables, adding work to every affected cache read without a documented or demonstrated need. The current top-level behavior is source-faithful and covered, so no change is needed.

@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai I checked the complete review, including the outside-diff findings, nitpicks, and coverage warning. The three inline findings have individual replies on their threads. The remaining dispositions are:

  • AllTaggedCache::add() intentionally follows Laravel semantics: the positive-TTL path is the native atomic add() operation and is event-free, while the null-TTL path uses the documented forever() fallback and therefore emits its normal write events.
  • This repository requires ext-redis ^6.1, so null is the exact supported initial scan cursor. Replacing it with a helper in two mocks is cosmetic and does not broaden the supported matrix.
  • The Lua expiry literals are guarded against StoreContext::MAX_EXPIRY by focused tests. Sending the constant through ARGV on every operation would add protocol data and shift argument offsets across the script family for no runtime correctness benefit.
  • The two limiter paths are clearer with their small, explicit cleanup blocks. A shared abstraction would hide failure precedence without eliminating meaningful complexity.
  • The tagged stack writes have distinct operation inputs and sibling implementations. A local helper would make this class less direct while leaving the wider family inconsistent.
  • The docstring percentage is not a repository quality gate. Hypervel follows Laravel-style method documentation and intentionally avoids blanket docblocks that only restate native types and method names.

No source or test changes are warranted by these items.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@binaryfire Thanks for the thorough dispositions. I agree that these remaining items are intentional Laravel-compatible behavior or established project style, rather than defects requiring changes. I’m withdrawing those findings.

🐇💭


✏️ Learnings added
Learnt from: binaryfire
URL: https://github.com/hypervel/components/pull/455

Timestamp: 2026-07-26T16:29:04.052Z
Learning: In Hypervel Cache, `AllTaggedCache::add()` intentionally follows Laravel semantics: a positive TTL uses the native atomic Redis add path without cache events, while a null TTL delegates to `forever()` and emits its ordinary write events.

Learnt from: binaryfire
URL: https://github.com/hypervel/components/pull/455

Timestamp: 2026-07-26T16:29:04.052Z
Learning: Hypervel requires `ext-redis ^6.1`; `null` is the supported initial scan cursor. Replacing literal null cursor values in Redis mocks with a helper is cosmetic unless the supported version matrix changes.

Learnt from: binaryfire
URL: https://github.com/hypervel/components/pull/455

Timestamp: 2026-07-26T16:29:04.052Z
Learning: Hypervel Cache intentionally uses focused tests to keep Redis Lua expiry literals synchronized with `Hypervel\Cache\Redis\Support\StoreContext::MAX_EXPIRY`; passing that constant through ARGV solely to avoid literals is not preferred.

Learnt from: binaryfire
URL: https://github.com/hypervel/components/pull/455

Timestamp: 2026-07-26T16:29:04.052Z
Learning: For Hypervel Cache, prefer explicit cleanup flows where they preserve callback-versus-release failure precedence, and avoid local abstractions for tagged stack writes when operation inputs and sibling implementations differ.

Learnt from: binaryfire
URL: https://github.com/hypervel/components/pull/455

Timestamp: 2026-07-26T16:29:04.052Z
Learning: Hypervel follows Laravel-style API documentation and intentionally avoids blanket docblocks that only restate native types and method names; documentation percentage is not a repository quality gate.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

That's a thorough trace and a solid justification. The upstream contract is explicitly top-level, the recursive expansion would require new traversal, cycle, and visibility rules with no demonstrated need, and adding that work to every affected cache read isn't a net win. The current behavior is correct as-is.

Update the displayed Hypervel documentation URL to match the existing hypervel.org target.

This removes the stale hypervel.com label so the package README presents one accurate canonical documentation address.
Record the completed Cache audit, including its final architecture, verified findings, accepted corrections, rejected speculative machinery, regression coverage, validation, and Laravel-facing result.

Mark Cache complete in the package checklist, route the next audit cycle to Session, and add the cache serialization finding to the cross-package revalidation index for its remaining consumers.
@binaryfire
binaryfire merged commit ced3bb6 into 0.4 Jul 26, 2026
38 checks passed
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.

1 participant