symfony/cache-contracts
Symfony Cache Contracts defines lightweight, PSR-friendly interfaces for cache and tag-aware caching, enabling consistent cache usage across Symfony components and third-party libraries. Use it to type-hint against stable APIs while swapping cache implementations.
No Installation Needed in Laravel: Since Laravel already includes symfony/cache-contracts as a transitive dependency (via symfony/cache), skip composer require. Focus on leveraging Laravel’s built-in Cache facade or dependency-injected interfaces.
First Use Case: Caching a Simple Value
// In a controller or service
$value = Cache::get('key');
if (!$value) {
$value = expensiveOperation();
Cache::put('key', $value, now()->addMinutes(10));
}
Under the hood, Laravel’s Cache facade uses Symfony’s CacheItemPoolInterface (PSR-6).
Where to Look First:
FilesystemAdapter, RedisAdapter).Inject Psr\Cache\CacheItemPoolInterface (or Symfony\Contracts\Cache\CacheInterface for simpler use cases) into services for testability and flexibility:
use Psr\Cache\CacheItemPoolInterface;
class MyService {
public function __construct(private CacheItemPoolInterface $cache) {}
public function fetchData() {
$item = $this->cache->getItem('data_key');
if (!$item->isHit()) {
$item->set(expensiveFetch());
$this->cache->save($item);
}
return $item->get();
}
}
Laravel-specific: Use Cache::store('redis')->getItemPool() to access a specific driver’s pool.
Leverage TagAwareCacheInterface (via symfony/cache's TagAwareAdapter) to invalidate related cache entries:
// In a service or event listener
Cache::tags(['products'])->flush(); // Clears all items tagged 'products'
Use Case: Invalidate product listings after a bulk update.
Combine adapters for resilience (e.g., in-memory fallback to Redis):
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
// In a service constructor
public function __construct(
AdapterInterface $primaryCache,
private ArrayAdapter $fallbackCache
) {
$this->primaryCache = $primaryCache;
}
// Usage
try {
$item = $this->primaryCache->getItem('key');
if (!$item->isHit()) {
throw new \RuntimeException('Cache miss');
}
} catch (\RuntimeException) {
$item = $this->fallbackCache->getItem('key');
// Fallback logic...
}
Batch operations with defer() and commit() for performance:
use Psr\Cache\CacheItemPoolInterface;
public function __construct(private CacheItemPoolInterface $cache) {}
public function batchSave(array $data) {
$items = [];
foreach ($data as $key => $value) {
$items[$key] = $this->cache->getItem($key);
$items[$key]->set($value);
}
$this->cache->commit(); // Atomic save
}
// config/cache.php
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'tags' => ['products', 'users'], // Enable tag support
],
];
Cache::remember() in middleware for route-level caching:
public function handle($request, Closure $next) {
return Cache::remember('homepage', now()->addHours(1), function () {
return $next($request);
});
}
symfony/cache-contracts alone provides caching. It only defines interfaces.symfony/cache, predis/predis for Redis, or doctrine/cache).Cache::driver('redis')—Laravel handles the adapter setup.symfony/cache version may not align with your custom adapter’s contract version.
symfony/cache-contracts:^2.0, but a custom adapter might target v1.0.composer.json:
"require": {
"symfony/cache-contracts": "^2.0",
"symfony/cache": "^6.0"
}
FilesystemAdapter: Serializes non-scalar values (e.g., objects) to JSON.ArrayAdapter: Stores data in-memory only (no persistence).Cache::store('array')->getItemPool() for testing, but avoid in production.$item->set(json_encode($complexObject));
ArrayAdapter resets on each request (useful for tests but misleading in dev).Cache::forget('key') or Cache::clear() to test invalidation.CacheItemPoolDecorator to log misses:
use Symfony\Component\Cache\Adapter\CacheItemPoolDecorator;
class LoggingCachePool extends CacheItemPoolDecorator {
public function getItem($key) {
$item = parent::getItem($key);
if (!$item->isHit()) {
Log::debug("Cache miss for key: $key");
}
return $item;
}
}
CacheItemPoolInterface (PSR-6) with CacheInterface (PSR-16).CacheItemPoolInterface) for advanced features (tags, deferred commits).CacheInterface) for simple key-value caching (e.g., get(), set()).Cache facade defaults to PSR-6 under the hood.CacheItemPoolInterface for a new backend (e.g., DynamoDB):
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\CacheItemInterface;
class DynamoDbCachePool implements CacheItemPoolInterface {
public function getItem($key): CacheItemInterface {
// Fetch from DynamoDB
return new DynamoDbCacheItem($key, $data);
}
// Implement other methods...
}
CacheItemPoolDecorator to add logic (e.g., logging, metrics):
use Symfony\Component\Cache\Adapter\CacheItemPoolDecorator;
class MetricsCachePool extends CacheItemPoolDecorator {
public function save(CacheItemInterface $item) {
$this->trackMetric('cache.save');
parent::save($item);
}
}
Cache::tags()).Cache::store('redis')->getEventDispatcher()->addListener() (advanced).Cache::shouldReceive('get')->andReturn(...) in PHPUnit with Mockery:
Cache::shouldReceive('get')
->with('key')
->andReturn('mocked_value');
How can I help you explore Laravel packages today?