iazaran / smart-cache
Smart Cache is a caching optimization package designed to enhance the way your Laravel application handles data caching. It intelligently manages large data sets by compressing, chunking, or applying other optimization strategies to keep your application performant and efficient.
Requires
- php: ^8.1
- illuminate/cache: ^8.0||^9.0||^10.0||^11.0||^12.0||^13.0
- illuminate/console: ^8.0||^9.0||^10.0||^11.0||^12.0||^13.0
- illuminate/contracts: ^8.0||^9.0||^10.0||^11.0||^12.0||^13.0
- illuminate/support: ^8.0||^9.0||^10.0||^11.0||^12.0||^13.0
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^6.0||^7.0||^8.0||^9.0||^10.0||^11.0
- phpunit/phpunit: ^9.6.33||^10.5.62||^11.5.50||^12.5.22||^13.1.6
- symfony/var-dumper: ^5.4|^6.0|^7.0|^8.0
Suggests
- ext-memcached: Recommended when using the Memcached cache driver for distributed caching
- ext-redis: Recommended when utilizing the Redis cache driver for better performance
- ext-zlib: Required for compressing cached data using gzip (strongly recommended)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-29 20:09:29 UTC
README
Laravel's cache, built for large payloads and high traffic. SmartCache sits on top of the cache store you already run (Redis, Memcached, DynamoDB, database, or file). It adds automatic compression, driver-safe chunking, stampede protection, invalidation that works on every driver, self-healing, and observability, and you keep the Cache API you already know.
- use Illuminate\Support\Facades\Cache; + use SmartCache\Facades\SmartCache;
PHP 8.1–8.5 · Laravel 8–13 · Redis, Memcached, DynamoDB, Database, File, Array
Why SmartCache?
Caching gets hard once an application grows. Payloads outgrow Memcached's 1 MB and DynamoDB's 400 KB item limits. Redis memory and bandwidth bills climb. A deploy or a flush sends every request to the database at once. File and database stores have no tags at all.
| Problem | Laravel cache alone | With SmartCache |
|---|---|---|
| Large payloads | Stored as one raw value | Compressed automatically (an API payload measured 97.6% smaller) |
| Driver item limits | You split values yourself | Transparent, key-preserving chunking of large lists |
| Cold-key stampedes | Every request regenerates | One process regenerates, the others wait (rememberWithLock()) |
| Stale-while-revalidate | flexible() on Laravel 11+ |
swr(), stale(), refreshAhead(), and queued refreshes on Laravel 8+ |
| Invalidation | Tags only on Redis, Memcached, and array stores | Tags, dependencies, patterns, and model hooks on every driver |
| Corrupted entries | A broken value or an exception | The entry is evicted and regenerated |
| Redundant writes | Every put() hits the store |
Unchanged values are skipped |
| Visibility | Build it yourself | Metrics, health score, events, audit, and benchmark commands |
Built for production
- Drop-in, and adopt it gradually. SmartCache implements Laravel's
Illuminate\Contracts\Cache\Repository(and so PSR-16), including enum keys, the typed getters,flexible(), andtags(). Switch one import where it helps, and keepCacheeverywhere else.SmartCache::repository()gives raw store access when you need it. - Safe to upgrade. It follows semantic versioning and needs no configuration. New features are opt-in, every release lists its upgrade notes, and adjacent releases read each other's entries, so rolling deploys work.
- Tested across the support matrix. CI runs 700+ tests on PHP 8.1–8.5 with Laravel 8–13, on every combination Laravel itself supports.
- Correct under concurrency. Readers never get a mix of two chunked versions. Shared indexes are updated under short atomic locks where the store supports them. Regeneration can be single-flight.
- Ready for long-running processes. On Octane, Swoole, FrankenPHP, RoadRunner, and queue workers, per-request state is reset automatically.
- Secure by default. Data stays in your own cache store: no external service and no telemetry. Optional encryption protects selected keys at rest,
serializable_classesextends Laravel's deserialization hardening to optimized values, and the web dashboard is off by default. - Observable. Hit ratios, compression savings, cost-aware eviction scores, events, and
smart-cache:auditshow what the cache is doing.
Installation
composer require iazaran/smart-cache
That's it: the package auto-registers and uses your default cache store. Enable ext-zlib for gzip compression (recommended).
Quick start
use SmartCache\Facades\SmartCache; SmartCache::put('users', $users, 3600); $users = SmartCache::get('users'); $report = SmartCache::remember('report', 3600, fn () => Report::build()); smart_cache(['products' => $products], 3600); // helper $products = smart_cache('products');
Values over 50 KB are compressed. Arrays and collections with more than 1,000 items (chunk_size) and over 100 KB are split into chunks instead. Smaller values are stored as they are.
Features
Stampede protection
// One process regenerates a missing key; the others wait, then read it $report = SmartCache::rememberWithLock('daily_report', 3600, fn () => Report::build()); // Probabilistic early refresh (XFetch), rate-limited regeneration, TTL jitter $data = SmartCache::rememberWithStampedeProtection('key', 3600, fn () => expensiveQuery()); SmartCache::throttle('api_call', 10, 60, fn () => expensiveApiCall()); SmartCache::putWithJitter('popular', $data, 3600, 0.1); // TTL ±10%
rememberWithLock() falls back to running the callback when the store has no lock support or the wait times out, so it never fails where remember() would succeed.
Stale-while-revalidate
$repos = SmartCache::swr('github_repos', fn () => Http::get('...')->json(), 300, 900); $config = SmartCache::stale('site_config', fn () => Config::fromDatabase(), 3600, 86400); $stats = SmartCache::refreshAhead('analytics', fn () => Analytics::report(), 1800, 300); // Return stale data now and refresh on a queue worker (invokable class, not a Closure) $data = SmartCache::asyncSwr('dashboard', RefreshDashboardStats::class, 300, 900, 'cache-refresh');
swr(), stale(), and refreshAhead() refresh in the current process before returning the stale value. Enable swr.single_flight to let only one worker refresh a key at a time.
Invalidation on every driver
SmartCache::tags(['users'])->put('user_1', $user, 3600); SmartCache::flushTags(['users']); // or SmartCache::tags(['users'])->flush() SmartCache::dependsOn('user_posts', 'user_profile'); SmartCache::invalidate('user_profile'); // also clears user_posts SmartCache::flushPatterns(['user_*', '/product_\d+/']); SmartCache::namespace('tenant_42')->put('settings', $settings, 3600); SmartCache::flushNamespace('tenant_42');
Models can invalidate their own entries:
class User extends Model { use \SmartCache\Traits\CacheInvalidation; protected function cacheInvalidation(): array { return [ 'keys' => ['user_{id}_profile'], 'tags' => ['users', 'team_{team_id}'], 'patterns' => ['users_list_*'], ]; } }
Model invalidation runs after the database transaction commits. Eloquent events do not fire for query-builder update()/delete(), upsert(), or raw SQL; call User::flushCacheTags() or SmartCache::flushTags() after those.
Resilience
- Self-healing. A corrupted entry, or a chunked entry with a missing chunk, is evicted instead of returned, so
remember()regenerates it. - Write deduplication. A
put()of unchanged content is skipped when the stored entry already lives long enough. - Circuit breaker and fallback.
SmartCache::withFallback(fn () => SmartCache::get('key'), fn () => $default)serves a fallback while the backend is failing (opt-in). - Conditional caching.
rememberIf($key, $ttl, $callback, fn ($value) => !empty($value))keeps error payloads out of the cache.
In-request memoization
$memo = SmartCache::memo(); $users = $memo->remember('users', 3600, fn () => User::all()); $users = $memo->get('users'); // served from memory
On Laravel 12.9+, set memo.native = true to share one memo layer per store with Laravel's Cache::memo().
Encryption at rest
// config/smart-cache.php → strategies 'encryption' => [ 'enabled' => true, 'keys' => ['user_token_abc123'], 'patterns' => ['/^user_token_/', '/^payment_/'], // PCRE regexes, not globs ],
Matching values are always encrypted with your application key, whatever their size.
Observability
SmartCache::getPerformanceMetrics(); // hit ratio, compression savings, timings SmartCache::analyzePerformance(); // health score and recommendations SmartCache::suggestEvictions(5); // lowest-value entries (cost-aware scoring)
php artisan smart-cache:status php artisan smart-cache:audit --driver=redis # broken chunks, orphans, large unoptimized values php artisan smart-cache:bench --profile=api-json # measure your own driver and payloads php artisan smart-cache:warm --warmer=products php artisan smart-cache:cleanup-chunks
Events (CacheHit, CacheMissed, KeyWritten, KeyForgotten, OptimizationApplied, TagFlushed) are available with events.enabled = true. There is also an optional web dashboard. It is disabled by default; if you enable it, protect it with authentication middleware such as ['web', 'auth'], because cache keys often contain user or tenant identifiers.
Benchmarks
Run php artisan smart-cache:bench against your own store and payloads. On the included PHP 8.4 / Laravel 13 / Redis run (docs/benchmark-report-redis.json), the api-json profile went from 323,811 to 7,829 bytes (97.58% smaller), chunked payloads kept their keys, and small payloads were left untouched.
Configuration
No configuration is needed to start. To change the defaults, publish the config file:
php artisan vendor:publish --tag=smart-cache-config
// config/smart-cache.php (excerpt) 'thresholds' => ['compression' => 1024 * 50, 'chunking' => 1024 * 100], 'strategies' => [ 'compression' => ['enabled' => true, 'mode' => 'fixed', 'level' => 6], 'chunking' => ['enabled' => true, 'chunk_size' => 1000, 'lazy_loading' => false], 'encryption' => ['enabled' => false, 'keys' => [], 'patterns' => []], ], 'deduplication' => ['enabled' => true], 'self_healing' => ['enabled' => true], 'swr' => ['single_flight' => false], 'tags' => ['scoped' => false], // recommended: true for new applications 'memo' => ['native' => false], 'serializable_classes' => null, // 'cache' applies Laravel's cache.serializable_classes 'dashboard' => ['enabled' => false, 'middleware' => ['web']],
See the configuration reference for every option.
Good to know
Cache clearing semantics
SmartCache::clearManaged(); // remove the entries SmartCache tracks SmartCache::flush(); // flush the entire underlying store
For backward compatibility, clear() behaves like clearManaged() in SmartCache 1.x, while PSR-16 clear() normally empties the whole pool. Choose clearManaged() or flush() explicitly when you migrate. This is the main intentional difference from Laravel's cache.
Tags
Tags work on every store because SmartCache keeps its own tag index. Tags set with tags() stay pending until a write uses them. With the default tags.scoped = false, a tagged get() that misses therefore also tags the next write, which only causes extra invalidation. New applications should set tags.scoped = true: tags then apply only to the keys looked up while they were pending.
Operational footprint
SmartCache stores small metadata entries (_sc_*) in the same store: the managed-key index, tag and dependency indexes, deduplication records, metrics, and chunks. If applications share infrastructure, use a dedicated store or cache prefix. For very large key spaces, cap the index with managed_keys.max_tracked.
- On the database cache driver,
rememberWithLock()and SmartCache's metadata locks need thecache_lockstable (php artisan cache:table). Without it they run without the lock, andrememberWithLock()logs a warning on every miss. smart-cache:clear --forcedeletes every key in the store that SmartCache does not manage (SmartCache's own_sc_*keys excepted), much likecache:clear. Check what shares the store before running it.
When SmartCache is not a fit
If most of your values are small and Laravel's cache already meets your latency and cost goals, or your payloads are already compressed or encrypted, the native cache is enough. You can still use SmartCache only for the keys that need it.
Upgrading
Every 1.x release keeps the same API and reads the entries the previous release wrote. Read the CHANGELOG upgrade notes for each release before a production rollout.
Documentation
Full documentation →: installation, the complete API reference, SWR patterns, invalidation, Octane and queue workers, and troubleshooting.
Testing
composer test
See TESTING.md.
Contributing and security
Contributions are welcome; see CONTRIBUTING.md. Report vulnerabilities privately as described in SECURITY.md.
License
MIT. See LICENSE.