cache/chain-adapter
PSR-6 cache pool chain adapter that combines multiple cache pools (e.g., APCu + Redis) into a single CachePoolChain. Part of PHP-Cache, with optional features like tagging and hierarchy via shared docs. Install with composer and use with minimal setup.
Installation:
composer require cache/chain-adapter
Basic Usage:
use Cache\ChainAdapter\CachePoolChain;
use Psr\Cache\CacheItemPoolInterface;
// Define your PSR-6 cache pools (e.g., Redis, APCu, FileCache)
$redisPool = new RedisCachePool($redisClient);
$apcuPool = new ApcuCachePool();
// Chain them in order of preference (first pool is highest priority)
$chainPool = new CachePoolChain([$apcuPool, $redisPool]);
First Use Case:
CacheItemPoolInterface in Laravel (e.g., in Cache facade or service containers).Cache::set('key', 'value', now()->addHour()); // Uses the chain pool
Fallback Strategy:
ApcuCachePool → RedisCachePool → FileCachePool).Tag-Based Operations:
$item = $chainPool->getItem('user:123');
$item->setTags(['users', 'premium']);
$chainPool->save($item);
Laravel Integration:
Cache facade or service container:
$app->bind(CacheItemPoolInterface::class, function ($app) {
return new CachePoolChain([
new ApcuCachePool(),
new RedisCachePool($app->make(Redis::class)),
]);
});
Conditional Pool Skipping:
skip_on_failure option to bypass failed pools:
$chainPool = new CachePoolChain([$pool1, $pool2], [
'skip_on_failure' => true, // Skip failed pools instead of halting
]);
LoggerAware. Inject a PSR-3 logger for debugging:
$chainPool->setLogger($logger);
$mockPool1 = $this->createMock(CacheItemPoolInterface::class);
$mockPool2 = $this->createMock(CacheItemPoolInterface::class);
$chainPool = new CachePoolChain([$mockPool1, $mockPool2]);
$pools = [];
if (app()->environment('local')) {
$pools[] = new ApcuCachePool();
}
$pools[] = new RedisCachePool($redisClient);
$chainPool = new CachePoolChain($pools);
Order Matters:
new CachePoolChain([$redisPool, $apcuPool]); // APCu will rarely be used!
Tag Inconsistency:
Psr\Cache\CacheItemInterface::setTags(). Verify compatibility before relying on tags.Exception Handling:
PoolFailedException if a pool fails. Use skip_on_failure => true to bypass failures:
$chainPool = new CachePoolChain([$pool1, $pool2], ['skip_on_failure' => true]);
Memory Leaks:
Enable Logging:
$chainPool->setLogger(new MonologLogger($logHandler));
Logs will include which pool succeeded/failed for each operation.
Check for NoPoolAvailableException:
skip_on_failure is false. Handle gracefully:
try {
$chainPool->getItem('key')->get();
} catch (NoPoolAvailableException $e) {
// Fallback to database or generate dynamically
}
Custom Pool Wrappers:
class LoggingCachePool implements CacheItemPoolInterface {
private $delegate;
public function __construct(CacheItemPoolInterface $delegate) {
$this->delegate = $delegate;
}
public function getItem($key) {
$logger->info("Accessing key: $key");
return $this->delegate->getItem($key);
}
// Delegate other methods...
}
Then chain it:
$chainPool = new CachePoolChain([
new LoggingCachePool($apcuPool),
$redisPool,
]);
Dynamic Pool Addition:
$chainPool->addPool(new FileCachePool('/tmp/cache'));
Conditional Pool Activation:
Cache events to toggle pools:
Cache::extend('chain', function ($app) {
$pools = [new ApcuCachePool()];
if (config('cache.redis.enabled')) {
$pools[] = new RedisCachePool($app->make(Redis::class));
}
return new CachePoolChain($pools);
});
How can I help you explore Laravel packages today?