定制 iprodev/php-easycache 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

iprodev/php-easycache

最新稳定版本:v3.0.2

Composer 安装命令:

composer require iprodev/php-easycache

包简介

PHP EasyCache is a lightweight, multi-backend (APCu/Redis/File/PDO) with SWR caching library that speeds up repeated API calls with thread‑safe writes, per‑key TTLs, automatic cleanup, key sanitization, and optional compression.

README 文档

README

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:

📦 Installation

composer require iprodev/php-easycache

Optional dependencies

  • ext-apcu for the APCu tier
  • ext-redis or predis/predis:^2.0 for the Redis tier
  • ext-zlib for Gzip compression
  • ext-zstd for Zstd compression

🚀 Quick Start (PSR‑16)

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

🎯 Core Features

1. Multi-Tier Caching

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

2. Stale-While-Revalidate (SWR)

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:

  1. If data is fresh, it's returned immediately
  2. If data is expired but within SWR window:
    • Stale data is returned instantly
    • Background refresh is triggered (non-blocking)
  3. If refresh fails, stale data continues to be served (within staleIfError window)

3. Pluggable Serialization

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

4. Pluggable Compression

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

💾 Storage Backends

APCu Storage

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

Redis Storage

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

File Storage

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

PDO Storage

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

🎨 Complete Examples

Example 1: Simple File Cache

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

Example 2: Multi-Tier with Backfill

// 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!

Example 3: SWR for API Responses

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

Example 4: Batch Operations

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

Example 5: DateInterval TTL

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

Example 6: Custom Logger Integration

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

Example 7: Scheduled Cleanup

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

🎭 Laravel Integration

Setup

  1. Install the package:
composer require iprodev/php-easycache
  1. Publish configuration:
php artisan vendor:publish --tag=easycache-config
  1. 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),
    ],
];

Using the Facade

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

Artisan Commands

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

🔑 Key Rules (PSR‑16)

  • 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

🧪 Testing & Quality Assurance

Running Tests

# Run all tests
composer test

# Run with coverage
composer test -- --coverage-html coverage

# Run specific test suite
./vendor/bin/phpunit --testsuite "Storage Tests"

Static Analysis

# Run PHPStan
composer stan

# Check coding standards
composer cs

# Fix coding standards automatically
composer cs:fix

Test Coverage

The 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

🔧 Advanced Configuration

Custom Lock Path

$cache = new MultiTierCache(
    [$storage],
    $serializer,
    $compressor,
    3600,
    $logger,
    '/custom/lock/path' // Custom lock directory
);

File Storage Sharding Levels

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

Environment Variables (Laravel)

# .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=600

🚨 Error Handling

All 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

🔄 Backwards Compatibility

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

📝 Best Practices

  1. Use multi-tier wisely: APCu → Redis → File/PDO
  2. Set appropriate TTLs: Balance freshness vs. performance
  3. Use SWR for expensive operations: Prevent cache stampedes
  4. Monitor cache hit rates: Use logging to track performance
  5. Schedule pruning: For PDO storage, prune regularly
  6. Use compression for large data: GzipCompressor or ZstdCompressor
  7. Namespace your keys: Use prefixes to avoid collisions
  8. Test error scenarios: Ensure your app handles cache failures gracefully

🤝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

Development Setup

git clone https://github.com/iprodev/php-easycache.git
cd php-easycache
composer install
composer test

📄 License

MIT © iprodev

🔗 Links

💬 Support

统计信息

  • 总下载量: 462
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 11
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 11
  • Watchers: 2
  • Forks: 3
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2015-07-06