iazaran/smart-cache
Drop-in replacement for Laravel’s Cache facade that automatically compresses and chunks large values, deduplicates unchanged writes, self-heals corrupted entries, and performs cost-aware eviction. Works with existing code (PHP 8.1+, Laravel 8–13).
guzzlehttp/guzzle 7.15.1, guzzlehttp/promises 2.5.1, and guzzlehttp/psr7 2.13.0 to resolve four upstream advisories reported on 2026-07-20. SmartCache does not directly require Guzzle; the packages are present through the Laravel development/test dependency graph. composer audit is clean after the update.SmartCache::clearManaged() as an explicit concrete-class and facade API for removing SmartCache-tracked entries without flushing unrelated keys. The public SmartCache contract is unchanged so third-party implementations remain compatible.clear() behavior while flush() clears the entire underlying store. Existing behavior is unchanged; new application code should prefer the scope-explicit clearManaged() or flush() methods.ext-json suggestion and installation prerequisite because JSON is always enabled in PHP 8+.setAccessible() calls, which have no effect on the supported PHP 8.1+ range and emit deprecation warnings on PHP 8.5.asyncSwr() Closure examples with serializable invokable-class examples; queued refresh callbacks reject closures by design.swr(), stale(), and refreshAhead() from the queue-backed asyncSwr() method, and corrected rollback guidance for optimized cache wrappers.xxh128 algorithm used since 1.12.1.cacheInvalidation(): array method. Existing fluent setters still work and are merged with declared rules.Model::flushCacheTags() for explicit invalidation after saveQuietly(), query-builder updates/deletes, upserts, mass inserts, raw SQL, or any other path that bypasses Eloquent events.TagFlushed event, including the tag name, live key count, and source (manual, model, or model_helper), when cache events are enabled.smart-cache.metadata_lock.*) and transaction-aware model invalidation (smart-cache.model_invalidation.after_commit).smart-cache.model_invalidation.after_commit to false to restore immediate invalidation.LockProvider, reducing lost tag-index updates under concurrent writers while preserving best-effort behavior for stores without locks.SmartCache::add() no longer leaks active tags into the next write when the atomic add fails because the key already exists.symfony/* lockfile entry from v8.0.8 to v8.1.0 (>= patched lines 8.0.12 / 8.0.13) to clear the rest of the open Dependabot advisories plus two pending CVEs surfaced by composer audit. Runtime: symfony/mailer (CVE-2026-45068, SendmailTransport argument injection via dash-prefixed recipient), symfony/routing (CVE-2026-45065, UrlGenerator route-requirement bypass via unanchored regex alternation; CVE-2026-48784, dot-segment encoding skip), symfony/http-foundation (CVE-2026-48736), symfony/http-kernel (CVE-2026-45075, #[IsGranted(methods: ['GET'])] filter bypass via HEAD). Dev: symfony/yaml (CVE-2026-45133 uncontrolled recursion, CVE-2026-45304 collection-alias "Billion Laughs", CVE-2026-45305 Parser::cleanup() ReDoS). composer audit is now clean across runtime and dev scopes. composer.json is unchanged — the existing ranges already permitted these versions; Dependabot was failing because of a stale resolver state on its side.symfony/mime from v8.0.8 to v8.1.0 (>= patched line 8.0.12) to address GHSA Email Header / SMTP Command Injection via CRLF in Symfony\Component\Mime\Address and Email Header Injection via Non-Token Characters in Mime Parameter Names. Transitive bumps: symfony/deprecation-contracts v3.6.0 → v3.7.0, symfony/polyfill-intl-idn v1.36.0 → v1.38.1, symfony/polyfill-intl-normalizer v1.36.0 → v1.38.0, symfony/polyfill-mbstring v1.36.0 → v1.38.1. No package API change.SmartCache::contentHash() (Cache DNA write-deduplication hot path) now uses xxh128 (PHP 8.1+, already a hard requirement) instead of md5. Output is still 32 lowercase hex characters, so the _sc_dna:{key} storage format is unchanged. Significantly faster on every put() when deduplication is enabled (default true). Existing _sc_dna:* entries from prior releases will mismatch once after upgrade and be transparently overwritten on the next put(); no errors, no data corruption.tests/Unit/SmartCacheTest.php::test_cache_dna_hash_format_is_stable locks the stored DNA hash contract (32 lowercase hex characters, deterministic for identical inputs, sensitive to value changes) so a future algorithm swap that breaks the key-length assumption is caught immediately.CompressionStrategy::restore() now explicitly validates the data field, the base64 decode step, the gzdecode() decompression step, and the unserialize() step, throwing RuntimeException on any failure. Previously a corrupted compressed payload could surface as a silent PHP warning followed by a false/garbage return value, which the cache layer would then re-cache. The unserialize() call is now wrapped with a temporary error handler so corrupted payloads no longer leak E_NOTICE warnings into application logs (round-tripping the value false still works).SmartCache::maybeRestoreValue() self-healing now evicts the full footprint of a corrupted entry: the wrapper key, the SWR/stampede metadata (_sc_meta:{key}), the Cache DNA hash (_sc_dna:{key}), the managed-keys index entry, and — when the wrapper is a chunked value — every chunk key referenced by chunk_keys and the orphan-chunk registry entry. Previously the chunk keys could survive as orphans after a self-heal pass.BackgroundCacheRefreshJob::__construct() now rejects Closure callbacks up-front with a clear InvalidArgumentException ("does not accept Closures …") instead of failing later inside Laravel's queue serializer with a generic "Serialization of 'Closure' is not allowed" error. The callable|string signature is unchanged; only the runtime guard is new.smart-cache.swr.single_flight = true and the underlying cache store implements Illuminate\Contracts\Cache\LockProvider (redis, memcached, database, dynamodb, file/array via lock files), refreshInBackground() now acquires a short non-blocking lock keyed on _sc_swr_refresh:{key} so only one worker regenerates a stale entry. Concurrent workers continue to serve the stale value without piling up redundant callback executions. Default false preserves the historical behaviour.SmartCache::reset() — a new public method that clears all per-request state (activeTags, activeNamespace, dirty flags, in-memory performance-metric buffers, managed-keys load flag). The service provider now calls reset() from its terminating() hook so Laravel Octane, Swoole, FrankenPHP, and RoadRunner workers no longer leak tag/namespace state between requests. The hook is a no-op outside long-running runtimes.smart-cache.managed_keys.max_tracked (default 0 = unlimited) caps the in-memory _sc_managed_keys index. When exceeded, the oldest entries are dropped FIFO to prevent the index from growing without bound in high-cardinality workloads. Default behaviour is unchanged.OrphanChunkCleanupService now accepts a persistEvery constructor argument (default 1 = persist every change, current behaviour). When raised, registry mutations buffer in memory and flush every N changes. The service provider always calls flush() from terminating() so buffered changes are not lost between requests.smart-cache.circuit_breaker.shared = true, the breaker mirrors its state (state, failure_count, success_count, opened_at) to a shared cache key (_sc_circuit_breaker:{driver}, TTL smart-cache.circuit_breaker.shared_ttl, default 300s) so all workers in a pool observe the same OPEN/CLOSED/HALF_OPEN decision. Default false preserves per-instance behaviour.tests/Unit/V112FeaturesTest.php and tests/Unit/Strategies/CompressionStrategyTest.php covering: compression-decode failure paths (invalid base64, corrupted gzip stream, missing data field, corrupted serialized payload, no warning leakage), self-healing eviction of chunked and compressed wrappers, SWR single-flight lock behaviour (lock held → callback skipped; disabled flag → synchronous refresh), reset() clearing namespace/tag state, BackgroundCacheRefreshJob closure rejection, bounded managed-keys cap, BC-safe unbounded default, debounced registry persistence + flush(), shared circuit-breaker visibility across instances, per-instance default, and SWR meta-key TTL co-residency.config/smart-cache.php documents the four new opt-in keys (swr.single_flight, managed_keys.max_tracked, circuit_breaker.shared, circuit_breaker.shared_ttl). All defaults preserve v1.11.0 behaviour.README.md and docs/index.html document the v1.12.0 changes, the Octane reset hook, the SWR single-flight option, and replace the static "tests-452 passed" badge with a real GitHub Actions CI badge.touch() now extends the TTL of every chunk key, the SWR/stampede metadata key (_sc_meta:{key}), and the Cache DNA hash key (_sc_dna:{key}) in addition to the wrapper key. Previously, calling touch() on a chunked entry left the underlying chunks scheduled to expire at their original TTL, which could surface as RuntimeException: Missing cache chunk […] on subsequent reads.touch() now returns false when the target key does not exist, matching Laravel cache semantics across both the native (Laravel 13+) and fallback paths.SmartSerializationStrategy::isJsonSafe() now performs a JSON encode/decode round-trip and rejects values whose decoded form does not strictly equal the original (e.g. stdClass collapsing to an empty array, Exception instances losing their class, and similar type-changing payloads). Forced-json mode degrades to php when the value cannot be safely round-tripped.JSON_PRESERVE_ZERO_FRACTION, so values like 1.0 round-trip as float instead of being silently coerced to int(1).isJsonSafe() rejects top-level resources, closures and non-stdClass objects upfront, and runs the round-trip check with JSON_THROW_ON_ERROR so that nested unsupported types do not emit unsuppressable E_WARNINGs into application logs.CostAwareCacheManager::trimIfNeeded() now trims down to 90% of max_tracked_keys instead of exactly the cap, amortising the arsort() cost across multiple inserts. Memory ceiling is unchanged. Behaviour with max_tracked_keys < 1 is now well-defined (metadata is cleared).ChunkingStrategy::shouldApply() estimates value size by sampling the serialized bytes of up to five items instead of using a fixed 50-byte-per-item heuristic, producing more accurate chunk decisions for non-trivial item sizes while keeping the borderline-case full-serialize fallback intact.SmartChunkSizeCalculator::calculateAverageItemSize() walks the first N items instead of calling array_rand(), removing RNG overhead and the is_array($samples) defensive branch..gitignore now excludes the .codex directory used by AI tooling.touch() (happy path and chunk-failure path), JSON_PRESERVE_ZERO_FRACTION preservation, stdClass/Exception/nested-object fallbacks, forced-json graceful degradation, legacy JSON payload restore compatibility, and dedicated tests verifying isJsonSafe() does not emit warnings for top-level resources, top-level closures, or nested resources.touch() on chunked entries (value still resolves and every chunk key survives) and touch() returning false for missing keys.CostAwareCacheManager covering cost-based scoring, the new 90%-of-capacity trimming behaviour, the max_tracked_keys = 1 edge case, and persist/load round-trip.smart-cache:audit for read-only diagnostics of managed keys, missing tracked keys, broken chunked entries, orphan chunks, large unoptimized values, and cost-aware eviction suggestions.smart-cache:bench for benchmarking raw Laravel cache operations against SmartCache optimization profiles, with table output, JSON output, driver selection, profile selection, iteration control, report-file export, and per-profile goal/result summaries.docs/benchmark-report-redis.json, generated from the package itself with PHP 8.4, Laravel 13, Redis, and ten iterations.README.md, docs/index.html, and TESTING.md with audit and benchmark workflows, local benchmark guidance, and the expanded test count.SmartCache::getAvailableCommands().remember() can regenerate clean data instead of returning a cached null.SECURITY.md for standardized enterprise vulnerability disclosures.CHANGELOG.md following the Keep a Changelog standard..editorconfig to enforce formatting consistency across contributors.config/smart-cache.php inline documentation for advanced strategies like adaptive mode and circuit_breaker.ext-zlib and ext-json extension suggestions to composer.json.CONTRIBUTING.md with test commands, PSR-12 standards, and security disclosure references.zlib extension to CI workflows for explicit compression test coverage.README.md with deep-dive troubleshooting and "Best Practices" examples.composer.lock to .gitignore (library best practice)./stats → /statistics) in README and docs/index.html.touch() method functionality and boot-safe event registration to comply with Laravel 13 architectures.add() and addCommand().README.md and documentation files for a better Developer Experience (DX).composer.json and meta-tags.store() method support directly on the SmartCache facade.docs/index.html and README.md.flexible macro implementation.flexible logic was not operating as expected under certain payloads.flexible macros.smart_cache global helper function to provide a drop-in analogue for Laravel's cache helper.How can I help you explore Laravel packages today?