composer require graham-campbell/bounded-cache:^3.1
config/cache.php:
'stores' => [
'bounded' => [
'driver' => 'bounded',
'connection' => 'file', // or any PSR-16-compliant store
'min_ttl' => 60, // Optional: Default min TTL in seconds
'max_ttl' => 300, // Optional: Default max TTL in seconds
],
],
AppServiceProvider:
use GrahamCampbell\BoundedCache\BoundedCache;
use GrahamCampbell\BoundedCache\BoundedCacheInterface;
public function register()
{
Cache::extend('bounded', function ($app) {
$connection = Cache::connection('file'); // Underlying store
return new BoundedCache($connection, $app['config']['cache.stores.bounded.min_ttl'], $app['config']['cache.stores.bounded.max_ttl']);
});
}
$products = Cache::store('bounded')->remember('products', now()->addMinutes(15), function () {
return Product::all();
});
Psr\SimpleCache\CacheInterface, so it works with any PSR-16-compliant backend.Cache::store('bounded') or extend the default cache via Cache::extend().min_ttl and max_ttl in config/cache.php for global defaults.// Set with explicit TTL (must respect min/max bounds)
Cache::store('bounded')->set('key', 'value', now()->addMinutes(30));
// Get with fallback
$value = Cache::store('bounded')->get('key', 'default');
// Remember (auto-respects bounds)
$value = Cache::store('bounded')->remember('key', now()->addMinutes(10), function () {
return expensiveOperation();
});
// Override min/max bounds for a specific key
$cache = new BoundedCache(Cache::store('file'), minTTL: 30, maxTTL: 600);
$cache->set('key', 'value', now()->addMinutes(120), minTTL: 60, maxTTL: 1200);
Cache::store('bounded')->tags(['products'])->put('featured', $products, now()->addMinutes(15));
Cache::forget() or Cache::tags() to manually evict keys.cache.hit or cache.miss events (if the underlying store emits them).Combine with other Laravel cache drivers:
// Use bounded cache for short-lived data, Redis for long-lived
if (config('app.env') === 'local') {
$cache = Cache::store('bounded');
} else {
$cache = Cache::store('redis');
}
Extend the package to create a reusable driver:
// app/Cache/BoundedRedisDriver.php
namespace App\Cache;
use GrahamCampbell\BoundedCache\BoundedCache;
use Illuminate\Cache\RedisStore;
class BoundedRedisDriver extends BoundedCache
{
public function __construct()
{
parent::__construct(new RedisStore(), minTTL: 60, maxTTL: 3600);
}
}
Register in AppServiceProvider:
Cache::extend('bounded_redis', function () {
return new BoundedRedisDriver();
});
Automatically apply bounded caching to API responses:
// app/Http/Middleware/BoundedCacheMiddleware.php
public function handle($request, Closure $next)
{
$response = $next($request);
if ($request->wantsJson() && !$request->is('health*')) {
$response->setCache([
'max_age' => 300, // 5 minutes
'shared_max_age' => 300,
'stale_while_revalidate' => 600,
]);
}
return $response;
}
Use Laravel’s Cache::shouldReceive() or mock the BoundedCache interface:
use GrahamCampbell\BoundedCache\BoundedCacheInterface;
$mock = Mockery::mock(BoundedCacheInterface::class);
$mock->shouldReceive('get')
->with('key')
->andReturn('cached_value');
$this->app->instance(BoundedCacheInterface::class, $mock);
TTL Bound Violations
min_ttl/max_ttl bounds, the package clamps the value to the nearest bound. For example:
$cache = new BoundedCache(Cache::store('file'), minTTL: 60, maxTTL: 300);
$cache->set('key', 'value', now()->addMinutes(5)); // TTL clamped to 60
$cache->set('key', 'value', now()->addHours(1)); // TTL clamped to 300
BoundedCacheInterface::setWithBounds() to explicitly set bounds per key.Underlying Store Limitations
file driver) has its own eviction policies, bounded TTLs may not work as expected. For example, a file cache might still hit disk limits.Race Conditions
set()/get() operations might lead to inconsistent TTL enforcement.Cache::lock() for critical sections or implement a distributed lock (e.g., Redis).Laravel Cache Events
cache:flushed or cache:hit events may not propagate through BoundedCache. If you rely on these, wrap the package in a decorator to emit custom events.BoundedCache to dispatch events:
use Illuminate\Support\Facades\Cache as LaravelCache;
class EventfulBoundedCache extends BoundedCache
{
public function get($key, $default = null)
{
$value = parent::get($key, $default);
if ($value !== $default) {
LaravelCache::dispatch('cache.hit', $key);
}
return $value;
}
}
PHP 8.1+ Features
new BoundedCache(..., minTTL: 60)). Ensure your Laravel app and dependencies support PHP 8.1+.php artisan package:discover to check for compatibility issues.Memory Bloat
maxTTL and monitor memory usage.cache:clear command or implement a cron job to periodically clear stale keys:
// app/Console/Commands/ClearBoundedCache.php
public function handle()
{
$cache = Cache::store('bounded');
$keys = $cache->getKeys(); // If supported by underlying store
foreach ($keys as $key) {
$cache->delete($key);
}
}
$cache = new BoundedCache(Cache::store('file'), minTTL: 60, maxTTL: 300);
$cache->setLogger(function ($message) {
\Log::debug('
How can I help you explore Laravel packages today?