symfony/cache
Symfony Cache provides fast, low-overhead PSR-6 and PSR-16 caching with adapters for popular backends, plus Symfony cache-contracts CacheInterface and TagAwareCacheInterface support.
{Minimal steps, where to look first, first use case}
To start using symfony/cache in Laravel, install the package via Composer:
composer require symfony/cache
Leverage the CacheInterface for a simple in-memory cache:
use Symfony\Component\Cache\Adapter\ArrayAdapter;
// Create a basic cache instance
$cache = new ArrayAdapter();
// Store and retrieve data
$item = $cache->getItem('key');
$item->set('value');
$item->expiresAfter(3600); // 1 hour TTL
$cache->save($item);
$value = $cache->getItem('key')->get();
Configure Redis via Laravel's cache config (config/cache.php) and use the adapter:
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Psr16Cache;
$redisClient = new \Redis();
$redisClient->connect('127.0.0.1', 6379);
$cache = new RedisAdapter($redisClient, 'laravel_cache', 0);
$psr16Cache = new Psr16Cache($cache); // PSR-16 wrapper
For database-backed caching:
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
use Doctrine\DBAL\Connection;
$connection = // Your DBAL connection;
$cache = new DoctrineDbalAdapter($connection, 'laravel_cache', 0);
Use CacheInterface for low-level cache operations:
// Get an item, compute on miss, and persist
$item = $cache->getItem('user_42_profile');
if (!$item->isHit()) {
$item->set(computeExpensiveValue());
$item->expiresAfter(3600);
$cache->save($item);
}
$value = $item->get();
// Delete and clear
$cache->deleteItem('user_42_profile');
$cache->clear();
Wrap PSR-6 cache in Psr16Cache for simplicity:
$psr16Cache = new Psr16Cache($cache);
$psr16Cache->set('key', 'value', 3600); // TTL in seconds
$value = $psr16Cache->get('key');
Use TagAwareAdapter for grouped cache invalidation:
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
// Create a tag-aware cache
$tagAwareCache = new TagAwareAdapter($cache);
// Store with tags
$item = $tagAwareCache->getItem('product_123');
$item->set($productData);
$item->tag(['products', 'category_electronics']);
$item->expiresAfter(3600);
$tagAwareCache->save($item);
// Invalidate by tag
$tagAwareCache->invalidateTags(['category_electronics']);
Combine multiple caches (e.g., APCu + Redis):
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
$localCache = new ApcuAdapter();
$persistentCache = new RedisAdapter($redisClient, 'redis_', 0);
$chainCache = new ChainAdapter([$localCache, $persistentCache]);
Prevent race conditions with LockFactory:
use Symfony\Component\Cache\Lock\LockFactory;
$lockFactory = new LockFactory($cache);
$lock = $lockFactory->createLock('unique_lock_key', 10); // 10-second TTL
if ($lock->acquire()) {
try {
// Critical section
} finally {
$lock->release();
}
}
Since v5.4.54, SodiumMarshaller gracefully handles values it cannot decrypt (e.g., after a key rotation) instead of throwing during unmarshalling. Wrap a real marshaller to add encryption on top of any adapter:
use Symfony\Component\Cache\Marshaller\SodiumMarshaller;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Adapter\RedisAdapter;
// $decryptionKey is a base64-encoded sodium key (indexable by a key identifier)
$marshaller = new SodiumMarshaller(
[$decryptionKey],
new DefaultMarshaller()
);
$cache = new RedisAdapter($redisClient, 'laravel_', 0, $marshaller);
Extend Laravel's cache manager:
// config/cache.php
'stores' => [
'symfony_redis' => [
'driver' => 'symfony',
'adapter' => 'redis',
'connection' => 'default',
'prefix' => 'laravel_',
],
],
// In a service provider
Cache::extend('symfony', function ($app) {
$redis = Redis::connection('default');
return new RedisAdapter($redis, 'laravel_', 0);
});
_/:. Invalid prefixes (e.g., /) will throw exceptions.
// Valid
$cache = new RedisAdapter($redis, 'laravel_', 0);
// Invalid (throws exception)
$cache = new RedisAdapter($redis, 'laravel/', 0);
dev_, prod_).TagAwareAdapter explicitly.TagAwareAdapter or use AbstractTagAwareAdapter.CacheItem::tag() on the item before save(); adding tags after save has no effect.ChainAdapter stops at the first successful cache hit (no fallback to next adapter if the first fails).ChainAdapter with a fast cache (e.g., APCu) first, followed by a persistent cache (e.g., Redis).// Bad: Risk of deadlock
$lock->acquire(true); // Block forever
// Good: Timeout after 10 seconds
$lock->acquire(false, 10);
LockFactory with LockInterface for distributed locking (e.g., Redis, DBAL).// Fails
$cache->set('key', $nonSerializableObject);
// Solution: Serialize manually
$cache->set('key', serialize($object));
Symfony\Component\Serializer\SerializerInterface for complex objects.SodiumMarshaller would throw when unmarshalling a value it could not decrypt (e.g., after rotating encryption keys, or reading data written with a key no longer in the rotation list). If you use SodiumMarshaller, upgrade to at least v5.4.54 — it now skips (treats as a cache miss) values it cannot decrypt, so decryption failures no longer bubble up as errors.SodiumMarshaller keys array so that existing entries remain readable after rotation; the new behavior just means a stale/corrupt entry becomes a miss rather than an exception.composer audit and pin a minimum constraint like "symfony/cache": ">=5.4.54" for this branch (or the equivalent patch on newer branches).// Sets TTL to 1 hour (3600 seconds)
$item->expiresAfter(3600);
RedisAdapter with RedisClusterAdapter for high availability:
$redisCluster = new \RedisCluster();
$cache = new RedisClusterAdapter($redisCluster, 'cluster_', 0);
CacheItem::isHit() to check if a cache item was found:
$item = $cache->getItem('key');
if (!$item->isHit()) {
$item->set(expensiveOperation());
$cache->save($item);
}
NullAdapter in tests when you want to force a miss without configuring a real backend.ArrayAdapter for development (fast, in-memory) and switch to RedisAdapter/DoctrineDbalAdapter for production.ApcuAdapter (if APCu is available).getItems() / saveDeferred() + commit() over per-key calls to reduce round trips.SodiumMarshaller (see §6) or your own application-level encryption.CacheItem::expiresAfter() with short TTLs for sensitive data:
$item->expiresAfter(60); // 1 minute
symfony/cache constraint patched; v5.4.54 is a security release.AbstractAdapter or AbstractTagAwareAdapter.use Symfony\Component\Cache\Adapter\AbstractAdapter;
class LoggingAdapter extends AbstractAdapter {
public function get($key, $default = null) {
\Log::debug("Cache hit for key: {$key}");
return parent::get($key, $default);
}
}
How can I help you explore Laravel packages today?