EasyCache is a pragmatic, batteries‑included cache library that implements the PSR‑16 Simple Cache interface and adds production‑grade features on top:
- 🚀 Multi‑tier storage: APCu, Redis, File, and PDO (MySQL/PostgreSQL/SQLite)
- 🔒 Atomic writes and read locks for file storage
- ⚡ Full SWR (stale‑while‑revalidate + stale‑if‑error), with non‑blocking per‑key locks
- 🔧 Pluggable Serializer & Compressor (PHP/JSON + None/Gzip/Zstd)
- 🔄 Automatic backfill between tiers (e.g., a Redis hit is written back to APCu)
- 🎯 First‑class Laravel integration via a Service Provider & Facade
- ✅ Comprehensive test coverage with PHPUnit
- 🛡️ Improved error handling with detailed logging support
Version: v3.0.1 — Requires PHP 8.1+ and
psr/simple-cache:^3.
📖 Documentation in other languages:
composer require iprodev/php-easycacheext-apcufor the APCu tierext-redisorpredis/predis:^2.0for the Redis tierext-zlibfor Gzip compressionext-zstdfor Zstd compression
use Iprodev\EasyCache\Cache\MultiTierCache;
use Iprodev\EasyCache\Storage\ApcuStorage;
use Iprodev\EasyCache\Storage\RedisStorage;
use Iprodev\EasyCache\Storage\FileStorage;
use Iprodev\EasyCache\Serialization\NativeSerializer;
use Iprodev\EasyCache\Compression\GzipCompressor;
// Tiers: APCu -> Redis -> File
$apcu = new ApcuStorage('ec:');
// phpredis (example); predis is also supported
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redisStore = new RedisStorage($redis, 'ec:');
$file = new FileStorage(__DIR__.'/cache');
$cache = new MultiTierCache(
[$apcu, $redisStore, $file],
new NativeSerializer(),
new GzipCompressor(3),
defaultTtl: 600
);
// PSR-16 API
$cache->set('user_42', ['id'=>42, 'name'=>'Ava'], 300);
$data = $cache->get('user_42'); // ['id'=>42, 'name'=>'Ava']Organize your cache in tiers from fastest to slowest. The library automatically:
- Reads from the fastest available tier
- Writes to all tiers
- Backfills faster tiers when data is found in slower tiers
// Example: Memory -> Redis -> Database
$cache = new MultiTierCache(
[
new ApcuStorage('app:'), // Fast: In-memory
new RedisStorage($redis), // Medium: Network cache
new PdoStorage($pdo, 'cache') // Slow: Database fallback
],
new NativeSerializer(),
new NullCompressor(),
3600 // 1 hour default TTL
);When data expires but is still inside the SWR window, stale data is served instantly while a refresh happens in the background. This prevents cache stampedes and ensures fast response times.
$result = $cache->getOrSetSWR(
key: 'posts_homepage',
producer: function () {
// Expensive API call or database query
return fetchPostsFromDatabase();
},
ttl: 300, // 5 minutes of fresh data
swrSeconds: 120, // Serve stale up to 2 minutes after expiry
staleIfErrorSeconds: 600, // If refresh fails, serve stale up to 10 minutes
options: ['mode' => 'defer'] // Defer refresh until after response
);How it works:
- If data is fresh, it's returned immediately
- If data is expired but within SWR window:
- Stale data is returned instantly
- Background refresh is triggered (non-blocking)
- If refresh fails, stale data continues to be served (within staleIfError window)
Choose the serializer that fits your needs:
// PHP Native Serializer (supports objects)
use Iprodev\EasyCache\Serialization\NativeSerializer;
$cache = new MultiTierCache([$storage], new NativeSerializer());
// JSON Serializer (portable, faster for simple data)
use Iprodev\EasyCache\Serialization\JsonSerializer;
$cache = new MultiTierCache([$storage], new JsonSerializer());Save memory and disk space:
// No compression
use Iprodev\EasyCache\Compression\NullCompressor;
$cache = new MultiTierCache([$storage], $serializer, new NullCompressor());
// Gzip compression (balanced)
use Iprodev\EasyCache\Compression\GzipCompressor;
$cache = new MultiTierCache([$storage], $serializer, new GzipCompressor(5));
// Zstd compression (fastest)
use Iprodev\EasyCache\Compression\ZstdCompressor;
$cache = new MultiTierCache([$storage], $serializer, new ZstdCompressor(3));Fast in-memory cache, perfect as the first tier.
use Iprodev\EasyCache\Storage\ApcuStorage;
$storage = new ApcuStorage(
prefix: 'myapp:' // Namespace your keys
);Features:
- Lightning-fast memory access
- Shared between PHP-FPM workers
- Automatic expiration
- Safe clear() that only deletes prefixed keys
Network-based cache with persistence options.
use Iprodev\EasyCache\Storage\RedisStorage;
// Using phpredis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$storage = new RedisStorage($redis, 'myapp:');
// Using predis
$redis = new Predis\Client('tcp://127.0.0.1:6379');
$storage = new RedisStorage($redis, 'myapp:');Features:
- Works with phpredis or predis
- TTL support with SETEX
- Safe clear() with prefix scanning
- Automatic expiration
Reliable disk-based cache with sharding.
use Iprodev\EasyCache\Storage\FileStorage;
$storage = new FileStorage(
path: '/var/cache/myapp', // Cache directory
ext: '.cache', // File extension
shards: 2 // Directory sharding level (0-3)
);Features:
- Atomic writes (temp file + rename)
- Read locks with flock()
- Directory sharding for performance
- Configurable file extension
Directory Sharding Example:
With shards=2, key "user_123" (hash: a1b2c3d4):
/var/cache/myapp/a1/b2/a1b2c3d4.cache
SQL database cache for shared environments.
use Iprodev\EasyCache\Storage\PdoStorage;
$pdo = new PDO('mysql:host=localhost;dbname=cache', 'user', 'pass');
$storage = new PdoStorage($pdo, 'easycache');
// Create table (run once during setup)
$storage->ensureTable();Supported databases:
- SQLite:
sqlite:/path/to/cache.db - MySQL:
mysql:host=localhost;dbname=cache - PostgreSQL:
pgsql:host=localhost;dbname=cache
Features:
- TTL support with expiration check
- Prune expired items with
prune() - UPSERT support (INSERT ... ON CONFLICT)
- Indexed queries for performance
use Iprodev\EasyCache\Cache\MultiTierCache;
use Iprodev\EasyCache\Storage\FileStorage;
use Iprodev\EasyCache\Serialization\NativeSerializer;
use Iprodev\EasyCache\Compression\NullCompressor;
$storage = new FileStorage(__DIR__ . '/cache');
$cache = new MultiTierCache([$storage], new NativeSerializer(), new NullCompressor());
// Set with 1 hour TTL
$cache->set('user_profile', [
'id' => 123,
'name' => 'John Doe',
'email' => 'john@example.com'
], 3600);
// Get
$profile = $cache->get('user_profile');
// Check existence
if ($cache->has('user_profile')) {
echo "Profile is cached!";
}
// Delete
$cache->delete('user_profile');// Setup: APCu (fast) -> Redis (medium) -> File (slow)
$apcu = new ApcuStorage('app:');
$redis = new RedisStorage($redisClient, 'app:');
$file = new FileStorage('/var/cache/app');
$cache = new MultiTierCache([$apcu, $redis, $file]);
// First request: Cache miss, data fetched and stored in all tiers
$data = $cache->get('expensive_data');
// APCu crashes and restarts...
// Next request: Data found in Redis, automatically backfilled to APCu
$data = $cache->get('expensive_data'); // Fast!use Psr\Log\LoggerInterface;
$cache = new MultiTierCache(
[$apcu, $redis],
new NativeSerializer(),
new GzipCompressor(5),
600, // 10 min default TTL
$logger // Optional PSR-3 logger
);
$posts = $cache->getOrSetSWR(
key: 'api_posts_latest',
producer: function() use ($apiClient) {
// This is expensive
return $apiClient->fetchPosts();
},
ttl: 300, // Fresh for 5 minutes
swrSeconds: 60, // Serve stale for 1 minute while refreshing
staleIfErrorSeconds: 300, // Serve stale for 5 minutes if API fails
options: ['mode' => 'defer'] // Refresh after response sent
);// Set multiple
$cache->setMultiple([
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
], 3600);
// Get multiple with default
$results = $cache->getMultiple(['key1', 'key2', 'missing'], 'default');
// ['key1' => 'value1', 'key2' => 'value2', 'missing' => 'default']
// Delete multiple
$cache->deleteMultiple(['key1', 'key2']);// Cache for 2 hours
$cache->set('key', 'value', new DateInterval('PT2H'));
// Cache for 1 day
$cache->set('key', 'value', new DateInterval('P1D'));
// Cache for 30 days
$cache->set('key', 'value', new DateInterval('P30D'));use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('cache');
$logger->pushHandler(new StreamHandler('/var/log/cache.log', Logger::WARNING));
$cache = new MultiTierCache(
[$storage],
new NativeSerializer(),
new NullCompressor(),
3600,
$logger // Will log warnings and errors
);// Run this in a cron job or scheduled task
$pruned = $cache->prune();
echo "Pruned {$pruned} expired items";
// For PDO storage, this removes expired rows
// For File/APCu/Redis, expiration is automatic- Install the package:
composer require iprodev/php-easycache- Publish configuration:
php artisan vendor:publish --tag=easycache-config- Configure in
config/easycache.php:
return [
'drivers' => ['apcu', 'redis', 'file'],
'default_ttl' => 600,
'serializer' => [
'driver' => 'php', // php|json
],
'compressor' => [
'driver' => 'gzip', // none|gzip|zstd
'level' => 5,
],
'redis' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
'password' => env('REDIS_PASSWORD', null),
'database' => env('REDIS_CACHE_DB', 1),
],
];use EasyCache;
// Simple operations
EasyCache::set('user_settings', $settings, 3600);
$settings = EasyCache::get('user_settings');
// SWR pattern
$data = EasyCache::getOrSetSWR(
'dashboard_stats',
fn() => $this->computeStats(),
300, // Fresh for 5 min
60, // SWR for 1 min
300 // Stale-if-error for 5 min
);
// Batch operations
EasyCache::setMultiple([
'key1' => 'value1',
'key2' => 'value2',
]);The package includes a prune command:
# Prune expired cache items
php artisan easycache:prune
# Add to your scheduler (app/Console/Kernel.php)
$schedule->command('easycache:prune')->daily();- Allowed characters:
[A-Za-z0-9_.] - Max length: 64 characters
- Reserved characters (not allowed):
{ } ( ) / \ @ :
// Valid keys
$cache->set('user_123', $data);
$cache->set('posts.latest', $data);
$cache->set('CamelCase', $data);
// Invalid keys (will throw InvalidArgument exception)
$cache->set('user:123', $data); // Contains :
$cache->set('user/123', $data); // Contains /
$cache->set('user@123', $data); // Contains @
$cache->set(str_repeat('x', 65), $data); // Too long# Run all tests
composer test
# Run with coverage
composer test -- --coverage-html coverage
# Run specific test suite
./vendor/bin/phpunit --testsuite "Storage Tests"# Run PHPStan
composer stan
# Check coding standards
composer cs
# Fix coding standards automatically
composer cs:fixThe library includes comprehensive tests for:
- ✅ All storage backends (File, APCu, Redis, PDO)
- ✅ Multi-tier caching with backfill
- ✅ SWR functionality
- ✅ Serializers (Native, JSON)
- ✅ Compressors (Null, Gzip, Zstd)
- ✅ Key validation
- ✅ Lock mechanism
- ✅ Edge cases and error handling
$cache = new MultiTierCache(
[$storage],
$serializer,
$compressor,
3600,
$logger,
'/custom/lock/path' // Custom lock directory
);// No sharding: /cache/md5hash.cache
$storage = new FileStorage('/cache', '.cache', 0);
// 1 level: /cache/a1/md5hash.cache
$storage = new FileStorage('/cache', '.cache', 1);
// 2 levels: /cache/a1/b2/md5hash.cache (recommended)
$storage = new FileStorage('/cache', '.cache', 2);
// 3 levels: /cache/a1/b2/c3/md5hash.cache
$storage = new FileStorage('/cache', '.cache', 3);# .env file
EASYCACHE_DRIVER=redis
EASYCACHE_REDIS_HOST=127.0.0.1
EASYCACHE_REDIS_PORT=6379
EASYCACHE_REDIS_PASSWORD=secret
EASYCACHE_REDIS_DB=1
EASYCACHE_DEFAULT_TTL=600All storage operations are wrapped with proper error handling. Failures are logged (if logger is provided) and don't crash your application:
use Monolog\Logger;
$logger = new Logger('cache');
$cache = new MultiTierCache([$storage], $serializer, $compressor, 3600, $logger);
// If storage fails, operation returns false but doesn't throw
$result = $cache->set('key', 'value');
if (!$result) {
// Check logs for details
echo "Cache set failed, check logs";
}Logged Events:
- Storage read/write failures
- Compression/decompression errors
- Lock acquisition failures
- SWR refresh errors
- Serialization errors
For projects upgrading from v2, use the BC wrapper:
use Iprodev\EasyCache\EasyCache;
$cache = new EasyCache([
'cache_path' => __DIR__ . '/cache',
'cache_extension' => '.cache',
'cache_time' => 3600,
'directory_shards' => 2,
]);
// Works like v2
$cache->set('key', 'value');
$value = $cache->get('key');- Use multi-tier wisely: APCu → Redis → File/PDO
- Set appropriate TTLs: Balance freshness vs. performance
- Use SWR for expensive operations: Prevent cache stampedes
- Monitor cache hit rates: Use logging to track performance
- Schedule pruning: For PDO storage, prune regularly
- Use compression for large data: GzipCompressor or ZstdCompressor
- Namespace your keys: Use prefixes to avoid collisions
- Test error scenarios: Ensure your app handles cache failures gracefully
Contributions are welcome! Please see CONTRIBUTING.md for details.
git clone https://github.com/iprodev/php-easycache.git
cd php-easycache
composer install
composer testMIT © iprodev
- Documentation
- API Reference - English
- API Reference - فارسی
- API Reference - کوردی
- Examples - English
- Examples - فارسی
- Examples - کوردی
- Changelog
- Security Policy
- Code of Conduct
- 📧 Email: support@iprodev.com
- 🐛 Issues: GitHub Issues
- 💡 Discussions: GitHub Discussions