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.

Maintainers

Package info

github.com/iazaran/smart-cache

pkg:composer/iazaran/smart-cache

Statistics

Installs: 9 262

Dependents: 0

Suggesters: 0

Stars: 211

Open Issues: 0

1.12.0 2026-05-21 17:02 UTC

README

Latest Version Total Downloads GitHub Stars License PHP Version Tests Coverage

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

Installation

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-cache

That's it. No further configuration is required — works immediately with your existing cache driver.

Quick Start

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.

Why SmartCache?

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

How Automatic Optimization Works

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.

Production Safety for Large Data

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 null is 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.

Features

Every feature below is opt-in and backward-compatible.

Multiple Cache Drivers

// 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);

SWR Patterns (Stale-While-Revalidate)

// 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() and refreshAhead() 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. Use asyncSwr() with a Laravel queue worker if you need the refresh to run in a separate process. asyncSwr() does not accept Closure callbacks — pass either a serializable invokable class or a "Class@method" string. Closures throw InvalidArgumentException since 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 = true to wrap the synchronous refresh in an opportunistic non-blocking lock keyed on _sc_swr_refresh:{key}. When the cache store implements LockProvider (redis, memcached, database, dynamodb, file) only one worker regenerates a stale entry; concurrent workers keep serving stale and return immediately. Default false preserves the historical "every worker refreshes" behaviour.

Stampede Protection

// 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 %)

Write Deduplication (Cache DNA)

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 unchanged

Self-Healing Cache

Corrupted 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());

Conditional Caching

$data = SmartCache::rememberIf('external_api', 3600,
    fn() => Http::get('https://api.example.com/data')->json(),
    fn($value) => !empty($value) && isset($value['status'])
);

Cost-Aware Eviction

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

Circuit Breaker & Fallback

$data = SmartCache::withFallback(
    fn() => SmartCache::get('key'),
    fn() => $this->fallbackSource()
);

In-Request Memoization

$memo = SmartCache::memo();
$users = $memo->remember('users', 3600, fn() => User::all());
$users = $memo->get('users'); // instant — served from memory

Atomic Locks

SmartCache::lock('expensive_operation', 10)->get(function () {
    return regenerateExpensiveData();
});

Namespacing

SmartCache::namespace('api_v2')->put('users', $users, 3600);
SmartCache::flushNamespace('api_v2');

Cache Invalidation

// 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']);

Model Auto-Invalidation

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_*'];
    }
}

Encryption at Rest

// config/smart-cache.php → strategies.encryption
'encryption' => [
    'enabled' => true,
    'keys' => ['user_token_abc123'],          // exact cache-key match
    'patterns' => ['/^user_token_/', '/^payment_/'],  // regex match
],

Adaptive Compression

config(['smart-cache.strategies.compression.mode' => 'adaptive']);
// Hot data → fast compression (level 3–4), cold data → high compression (level 7–9)

Lazy Loading

config(['smart-cache.strategies.chunking.lazy_loading' => true]);
$dataset = SmartCache::get('100k_records'); // LazyChunkedCollection
foreach ($dataset as $record) { /* max 3 chunks in memory */ }

Batch Operations

$values = SmartCache::many(['key1', 'key2', 'key3']);
SmartCache::putMany(['key1' => $a, 'key2' => $b], 3600);
SmartCache::deleteMultiple(['key1', 'key2', 'key3']);

Cache Events

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}"));

Monitoring & Dashboard

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/health
php 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-chunks

Cache Audit & Benchmarks

Use 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=50

It 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.json

A 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.

Best Practices & Troubleshooting

  • Binary Data: If caching already compressed objects like images, video, or encrypted data, disable compression for 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_limit in php.ini or enable lazy_loading via config to avoid server crashes.
  • Provider Not Found: Laravel aggressively caches service providers and aliases. Always run php artisan optimize:clear after 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 needing ide-helper generated files.

Configuration

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
];

Laravel Octane / Long-Running Workers

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.

Migration from Laravel Cache

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');

Documentation

Full documentation → — Installation, API reference, SWR patterns, and more.

Testing

composer test            # 470 tests, 1,931 assertions
composer test-coverage   # with code coverage

See TESTING.md for details.

Contributing

Please see CONTRIBUTING.md.

License

MIT — see LICENSE.

Links