Drop-in replacement for Laravel's Cache facade that automatically compresses, chunks, and optimizes cached data — with write deduplication, self-healing recovery, and cost-aware eviction built in.
Implements Illuminate\Contracts\Cache\Repository and PSR-16 SimpleCache. Your existing code works unchanged.
PHP 8.1+ · Laravel 8–13 · Redis, File, Database, Memcached, Array
Prerequisites: PHP extensions ext-zlib (for data compression) and ext-json (for optimal object serialization) are strongly recommended to enable all performance optimization strategies.
composer require iazaran/smart-cacheThat's it. No further configuration is required — works immediately with your existing cache driver.
use SmartCache\Facades\SmartCache;
// Same API you already know
SmartCache::put('users', $users, 3600);
$users = SmartCache::get('users');
// Remember pattern — with automatic compression & cost tracking
$users = SmartCache::remember('users', 3600, fn() => User::all());
// Helper function
smart_cache(['products' => $products], 3600);
$products = smart_cache('products');Large data is automatically compressed and chunked behind the scenes. No code changes needed.
| Problem | Without SmartCache | With SmartCache |
|---|---|---|
| Large payloads (100 KB+) | Stored as-is, slow reads | Auto-compressed & chunked |
| Redundant writes | Every put() hits the store |
Skipped when unchanged (write deduplication) |
| Corrupted entries | Exception crashes the request | Auto-evicted and regenerated, including broken chunk sets |
| Eviction decisions | LRU / random | Cost-aware scoring — keeps high-value keys |
| Cache stampede | Thundering herd on expiry | XFetch, jitter, and rate limiting |
| Conditional caching | Manual if around put() |
rememberIf() — one-liner |
| Stale data serving | Not available | SWR, stale, refresh-ahead, async queue refresh |
| Observability | DIY logging | Built-in dashboard, metrics, and health checks |
SmartCache selects the best strategy based on your data — zero configuration:
| Data Profile | Strategy Applied | Effect |
|---|---|---|
| Arrays with 5 000+ items | Chunking | Lower memory, faster access |
| Serialized data > 50 KB | Compression | Significant size reduction (gzip) |
| API responses > 100 KB | Chunking + Compression | Best of both |
| Data < 50 KB | None | Zero overhead |
All thresholds are configurable.
SmartCache is built for the painful cases that appear after an application grows: large Eloquent result sets, API payloads, reports, dashboards, and Redis/Memcached entries that get too big to manage safely.
- Data shape is preserved. Chunked payloads keep associative keys and sparse numeric keys intact, so ID-keyed arrays do not come back reindexed.
- Partial chunk loss is recoverable. If a chunk is missing or corrupted, SmartCache treats the entry as a cache miss, evicts the broken metadata, and lets
remember()regenerate a clean value. - Null remains a valid cached value. Stored
nullis distinguished from a miss, preserving Laravel cache semantics while still enabling self-healing. - Raw repository access is still available. Use
SmartCache::repository()when a package or one-off operation needs the underlying Laravel cache store directly.
Every feature below is opt-in and backward-compatible.
// Each store preserves all SmartCache optimizations
SmartCache::store('redis')->put('key', $value, 3600);
SmartCache::store('memcached')->remember('users', 3600, fn() => User::all());
// Bypass SmartCache when needed
SmartCache::repository('redis')->put('key', $value, 3600);// Serve stale data while refreshing in background
$data = SmartCache::swr('github_repos', fn() => Http::get('...')->json(), 300, 900);
// Extended stale serving (1 h fresh, 24 h stale)
$config = SmartCache::stale('site_config', fn() => Config::fromDatabase(), 3600, 86400);
// Proactive refresh before expiry
$analytics = SmartCache::refreshAhead('daily_analytics', fn() => Analytics::generateReport(), 1800, 300);
// Queue-based background refresh — returns stale immediately, refresh runs on a worker
$data = SmartCache::asyncSwr('dashboard_stats', fn() => Stats::generate(), 300, 900, 'cache-refresh');How "background" works.
swr(),stale()andrefreshAhead()run the refresh callback synchronously in the same PHP process after returning the stale value to the caller. This keeps the request hot path fast (the caller does not wait for the new value), but the worker still pays the cost of the regeneration. UseasyncSwr()with a Laravel queue worker if you need the refresh to run in a separate process.asyncSwr()does not acceptClosurecallbacks — pass either a serializable invokable class or a"Class@method"string. Closures throwInvalidArgumentExceptionsince v1.12.0 so the failure is loud at dispatch time instead of inside the queue serializer.Single-flight refresh (opt-in, v1.12.0+). Set
smart-cache.swr.single_flight = trueto wrap the synchronous refresh in an opportunistic non-blocking lock keyed on_sc_swr_refresh:{key}. When the cache store implementsLockProvider(redis, memcached, database, dynamodb, file) only one worker regenerates a stale entry; concurrent workers keep serving stale and return immediately. Defaultfalsepreserves the historical "every worker refreshes" behaviour.
// XFetch algorithm — probabilistic early refresh
$data = SmartCache::rememberWithStampedeProtection('key', 3600, fn() => expensiveQuery());
// Rate-limited regeneration
SmartCache::throttle('api_call', 10, 60, fn() => expensiveApiCall());
// TTL jitter — prevents thundering herd on expiry
SmartCache::withJitter(0.1)->put('popular_data', $data, 3600);
// Actual TTL: 3240–3960 s (±10 %)Hashes every value before writing. Identical content → write skipped entirely.
SmartCache::put('app_config', Config::all(), 3600);
SmartCache::put('app_config', Config::all(), 3600); // no I/O — data unchangedCorrupted entries are auto-evicted and regenerated on next read — zero downtime. This includes missing chunks from large chunked payloads.
$report = SmartCache::remember('report', 3600, fn() => Analytics::generate());$data = SmartCache::rememberIf('external_api', 3600,
fn() => Http::get('https://api.example.com/data')->json(),
fn($value) => !empty($value) && isset($value['status'])
);GreedyDual-Size–inspired scoring: score = (cost × ln(1 + access_count) × decay) / size
SmartCache::remember('analytics', 3600, fn() => AnalyticsService::generateReport());
SmartCache::getCacheValueReport(); // all entries ranked by value
SmartCache::suggestEvictions(5); // lowest-value entries to remove$data = SmartCache::withFallback(
fn() => SmartCache::get('key'),
fn() => $this->fallbackSource()
);$memo = SmartCache::memo();
$users = $memo->remember('users', 3600, fn() => User::all());
$users = $memo->get('users'); // instant — served from memorySmartCache::lock('expensive_operation', 10)->get(function () {
return regenerateExpensiveData();
});SmartCache::namespace('api_v2')->put('users', $users, 3600);
SmartCache::flushNamespace('api_v2');// Pattern-based
SmartCache::flushPatterns(['user_*', 'api_v2_*', '/product_\d+/']);
// Dependency tracking
SmartCache::dependsOn('user_posts', 'user_profile');
SmartCache::invalidate('user_profile'); // also clears user_posts
// Tag-based
SmartCache::tags(['users'])->put('user_1', $user, 3600);
SmartCache::flushTags(['users']);use SmartCache\Traits\CacheInvalidation;
class User extends Model
{
use CacheInvalidation;
public function getCacheKeysToInvalidate(): array
{
return ["user_{$this->id}_profile", "user_{$this->id}_posts", 'users_list_*'];
}
}// config/smart-cache.php → strategies.encryption
'encryption' => [
'enabled' => true,
'keys' => ['user_token_abc123'], // exact cache-key match
'patterns' => ['/^user_token_/', '/^payment_/'], // regex match
],config(['smart-cache.strategies.compression.mode' => 'adaptive']);
// Hot data → fast compression (level 3–4), cold data → high compression (level 7–9)config(['smart-cache.strategies.chunking.lazy_loading' => true]);
$dataset = SmartCache::get('100k_records'); // LazyChunkedCollection
foreach ($dataset as $record) { /* max 3 chunks in memory */ }$values = SmartCache::many(['key1', 'key2', 'key3']);
SmartCache::putMany(['key1' => $a, 'key2' => $b], 3600);
SmartCache::deleteMultiple(['key1', 'key2', 'key3']);config(['smart-cache.events.enabled' => true]);
Event::listen(CacheHit::class, fn($e) => Log::info("Hit: {$e->key}"));
Event::listen(CacheMissed::class, fn($e) => Log::warning("Miss: {$e->key}"));SmartCache::getPerformanceMetrics(); // hit_ratio, compression_savings, timing
SmartCache::analyzePerformance(); // health score + recommendations// Enable web dashboard
'dashboard' => ['enabled' => true, 'prefix' => 'smart-cache', 'middleware' => ['web', 'auth']],
// GET /smart-cache/dashboard | /smart-cache/statistics | /smart-cache/healthphp artisan smart-cache:status
php artisan smart-cache:audit --driver=redis
php artisan smart-cache:bench --driver=redis --iterations=5
php artisan smart-cache:clear
php artisan smart-cache:warm --warmer=products --warmer=categories
php artisan smart-cache:cleanup-chunksUse the audit command before production changes or after a cache incident:
php artisan smart-cache:audit
php artisan smart-cache:audit --format=json
php artisan smart-cache:audit --driver=redis --limit=50It reports managed keys, missing tracked keys, broken chunked entries, orphan chunks, large unoptimized values, and cost-aware eviction suggestions without mutating the cache.
Use the benchmark command to measure your own driver and payload behavior:
php artisan smart-cache:bench
php artisan smart-cache:bench --profile=api-json --driver=redis --iterations=10
php artisan smart-cache:bench --format=json --output=storage/smart-cache-bench.jsonA generated Redis report is included at docs/benchmark-report-redis.json. On the included PHP 8.4 / Laravel 13 / Redis run, the api-json profile compressed from 323,811 bytes to 7,829 bytes (97.58% smaller). Each profile includes a goal, success_metric, goal_passed, and result_summary field so compression is judged by byte reduction, chunking is judged by driver-safe splitting and key preservation, and small payloads are judged by avoiding unnecessary optimization.
- Binary Data: If caching already compressed objects like images, video, or encrypted data, disable
compressionfor those specific cache keys as they waste CPU cycles without yielding size reductions. - Memory Limits with Chunking: Large multi-megabyte datasets automatically trigger the 'chunking' strategy. For arrays over 100,000 items, verify
memory_limitinphp.inior enablelazy_loadingvia config to avoid server crashes. - Provider Not Found: Laravel aggressively caches service providers and aliases. Always run
php artisan optimize:clearafter upgrading or installing this package if encountering "Class 'SmartCache' not found". - IDE Autocomplete: Modern IDEs (PhpStorm, VSCode) completely resolve
SmartCache::magical methods automatically via our included Facade PHPDocs without needingide-helpergenerated files.
Publish the config file (optional — sensible defaults are applied automatically):
php artisan vendor:publish --tag=smart-cache-config// config/smart-cache.php (excerpt)
return [
'thresholds' => [
'compression' => 1024 * 50, // 50 KB
'chunking' => 1024 * 100, // 100 KB
],
'strategies' => [
'compression' => ['enabled' => true, 'mode' => 'fixed', 'level' => 6],
'chunking' => ['enabled' => true, 'chunk_size' => 1000],
'encryption' => ['enabled' => false, 'keys' => []],
],
'monitoring' => ['enabled' => true, 'metrics_ttl' => 3600],
'circuit_breaker' => [
'enabled' => false,
'failure_threshold' => 5,
'recovery_timeout' => 30,
'shared' => false, // v1.12.0: share breaker state across workers via the cache
'shared_ttl' => 300, // v1.12.0: TTL for the shared state key
],
'rate_limiter' => ['enabled' => true, 'window' => 60, 'max_attempts' => 10],
'jitter' => ['enabled' => false, 'percentage' => 0.1],
'deduplication' => ['enabled' => true], // Write deduplication (Cache DNA)
'self_healing' => ['enabled' => true], // Auto-evict corrupted entries
'swr' => ['single_flight' => false], // v1.12.0: opt-in single-flight refresh
'managed_keys' => ['max_tracked' => 0], // v1.12.0: 0 = unlimited (default)
'dashboard' => ['enabled' => false, 'prefix' => 'smart-cache', 'middleware' => ['web']],
'warmers' => [], // Cache warmer classes for smart-cache:warm
];SmartCache is registered as a singleton, which means per-request state (active tags, namespaces, in-memory metric buffers) would normally leak between requests on Laravel Octane, Swoole, FrankenPHP or RoadRunner. Since v1.12.0 the service provider's terminating() hook calls SmartCache::reset() and OrphanChunkCleanupService::flush() at the end of every request, so there is nothing extra to configure. If you embed SmartCache in your own long-running runtime, call app(\SmartCache\Contracts\SmartCache::class)->reset() between iterations.
Change one import — everything else stays the same:
- use Illuminate\Support\Facades\Cache;
+ use SmartCache\Facades\SmartCache;
SmartCache::put('users', $users, 3600);
$users = SmartCache::get('users');Full documentation → — Installation, API reference, SWR patterns, and more.
composer test # 470 tests, 1,931 assertions
composer test-coverage # with code coverageSee TESTING.md for details.
Please see CONTRIBUTING.md.
MIT — see LICENSE.