chillerlan/php-cache
A lightweight PSR-16 caching library for PHP offering pluggable storage adapters for Memcached, Redis, filesystem, and in-memory caches. Supports TTLs, namespacing, and simple key-value storage with minimal setup.
chillerlan/php-cache is a small, dependency-light PSR-16 implementation. It gives you a CacheInterface you can type-hint, plus a handful of drivers (memory, file, session, Redis, Memcached, null). There is no container, no event system, and no PSR-6 pooling — it is deliberately a thin layer over PHP’s cache primitives.
Install it with Composer:
composer require chillerlan/php-cache
The namespace is chillerlan\SimpleCache. If you only remember one thing, remember this: code against Psr\SimpleCache\CacheInterface, not against a concrete driver. That keeps the driver swappable between environments and tests.
First use case — an in-memory cache for a request or a test:
use Psr\SimpleCache\CacheInterface;
use chillerlan\SimpleCache\MemoryCache;
$cache = new MemoryCache;
$cache->set('greeting', 'hello', 60);
echo $cache->get('greeting', 'default'); // hello
For a real application, the file driver is the usual entry point:
use chillerlan\SimpleCache\FileCache;
$cache = new FileCache([
'filestorage' => __DIR__ . '/storage/cache',
]);
Where to look first:
src/ — MemoryCache, FileCache, SessionCache, RedisCache, MemcachedCache, NullCache.Bind it in Laravel’s container so application code never constructs drivers directly:
// AppServiceProvider::register()
$this->app->singleton(CacheInterface::class, function () {
return new FileCache([
'filestorage' => storage_path('framework/cache/chillerlan'),
]);
});
PSR-16’s get() default value cannot distinguish “missing” from “stored null”. Use a sentinel object:
final class Miss {}
function remember(CacheInterface $cache, string $key, int $ttl, Closure $resolve): mixed
{
$miss = new Miss;
$value = $cache->get($key, $miss);
if ($value !== $miss) {
return $value;
}
$value = $resolve();
$cache->set($key, $value, $ttl);
return $value;
}
This is the workhorse pattern: read, miss, compute, write, return.
Use the same interface everywhere and swap the implementation per environment:
MemoryCache (fast, isolated per process) or NullCache (always misses, disables caching entirely).FileCache.RedisCache or MemcachedCache.SessionCache or MemoryCache.Because everything is CacheInterface, this can be a one-line binding change.
PSR-16 has no tags, hierarchies, or prefix option. Encode structure in the key yourself:
$key = sprintf('user:%d:profile:%d', $userId, $version);
Reserved characters in PSR-16 keys are { } ( ) / \ @ :, and implementations may enforce a 64-character limit. Avoid raw user input in keys; hash or slugify it.
Use setMultiple() and getMultiple() instead of loops when priming a page:
$cache->setMultiple([
'home:stats' => $stats,
'home:popular' => $popular,
], 300);
$values = $cache->getMultiple(['home:stats', 'home:popular'], null);
If you need to invalidate “all user profile entries”, store a version counter and bake it into keys:
$version = $cache->get('user:profile:version', 1);
$key = "user:profile:v{$version}:{$userId}";
// invalidate the whole group:
$cache->set('user:profile:version', $version + 1);
Old entries become unreachable and expire naturally. This is the standard workaround for PSR-16’s lack of tags.
Pass integers (seconds) or DateInterval values. Be explicit about 0 and negative TTLs — PSR-16 says the item should be deleted, but confirm driver behavior. Prefer named constants or a small helper so TTLs don’t become magic numbers scattered across the codebase.
increment(), tag flushing, or stampede protection, you need a different abstraction or must build it yourself.get() null ambiguity. Stored null and a cache miss look the same with a null default. Use has() or the sentinel pattern above.clear() can be dangerous on shared stores. On Redis it may flush a database. Use a dedicated Redis database or instance for this cache; PSR-16 offers no built-in key prefix.MemoryCache may be rejected by a stricter backend. Validate and normalize keys in one place.composer.json, read the driver you use, and remember it is a thin layer — forking or writing a custom driver against CacheInterface is cheap.Psr\SimpleCache\CacheInterface or extend the package’s base cache class for custom backends (APCu, DynamoDB, etc.). Keep the same key/TTL conventions so the rest of your code doesn’t care.NullCache. It is useful for asserting that code paths do not depend on cache hits, and for disabling caching in integration tests without changing application wiring.How can I help you explore Laravel packages today?