astrotomic/laravel-cachable-attributes
Installation:
composer require astrotomic/laravel-cachable-attributes
Publish the config (optional, defaults are sensible):
php artisan vendor:publish --provider="Astrotomic\CachableAttributes\CachableAttributesServiceProvider"
Basic Usage:
Add the HasCachableAttributes trait to your Eloquent model:
use Astrotomic\CachableAttributes\HasCachableAttributes;
class User extends Model
{
use HasCachableAttributes;
// Your model code...
}
First Cachable Attribute:
Define a cached accessor using the cachableAttribute() method:
public function getFullNameAttribute(): string
{
return $this->cachableAttribute('full_name', function () {
return "{$this->first_name} {$this->last_name}";
});
}
Cache Invalidation: Automatically invalidates cache when related models change:
// In User model
public function profile()
{
return $this->hasOne(Profile::class);
}
// In Profile model
public function user()
{
return $this->belongsTo(User::class)->withCachableAttributes();
}
// Simple cached accessor
public function getFormattedAddressAttribute()
{
return $this->cachableAttribute('formatted_address', function () {
return "{$this->street}, {$this->city}, {$this->zip}";
});
}
// Cached accessor with TTL (time-to-live)
public function getProcessedDataAttribute()
{
return $this->cachableAttribute('processed_data', function () {
return $this->expensiveComputation();
}, now()->addMinutes(30));
}
Cache attributes conditionally:
public function getDynamicContentAttribute()
{
if ($this->shouldCacheContent()) {
return $this->cachableAttribute('dynamic_content', function () {
return $this->generateContent();
});
}
return $this->generateContent();
}
// In controller
$user = User::updateOrCreate(
['email' => $request->email],
[
'first_name' => $request->first_name,
'last_name' => $request->last_name,
// Cachable attributes will auto-update
]
);
// In controller
$users = User::withCachableAttributes(['full_name', 'formatted_address'])->get();
$user->invalidateCachableAttribute('full_name');
protected static function booted()
{
static::saved(function ($model) {
$model->invalidateAllCachableAttributes();
});
}
// Automatically invalidates when related model changes
public function profile()
{
return $this->hasOne(Profile::class)->withCachableAttributes();
}
Configure in config/cachable-attributes.php:
'store' => 'redis',
'prefix' => 'cache.attributes',
Use cached attributes in API responses for consistent performance:
return UserResource::collection(
User::withCachableAttributes(['full_name'])->get()
);
Cache expensive view logic:
// In User model
public function getDisplayNameAttribute()
{
return $this->cachableAttribute('display_name', function () {
return $this->first_name ?? $this->email;
});
}
// In view
<h1>{{ $user->display_name }}</h1>
Mock cache behavior in tests:
public function test_cachable_attribute()
{
$user = User::factory()->create();
Cache::shouldReceive('get')
->withArgs(['user.1.formatted_address'])
->andReturn('Cached Value');
$this->assertEquals('Cached Value', $user->formatted_address);
}
For long-running operations, defer cache updates:
public function update()
{
$this->fireModelEvent('updating');
// ... other updates ...
Cache::forget("user.{$this->id}.full_name");
$this->fireModelEvent('updated');
}
Cache Key Collisions:
$this->cachableAttribute('App\Models\User.formatted_address', ...);
Lazy Loading Overhead:
withCachableAttributes() in queries or eager-load explicitly.Serialization Issues:
serialize() or json_encode() in the accessor:
$this->cachableAttribute('serialized_data', function () {
return json_encode($this->complexData);
});
Cache Stampede:
Cache::rememberForever().Relationship Invalidation:
withCachableAttributes() on relationships can lead to stale cache.hasOne, belongsTo, etc.:
return $this->hasOne(Profile::class)->withCachableAttributes();
TTL Misconfiguration:
$ttl = $this->isActive() ? now()->addHours(1) : now()->addMinutes(5);
$this->cachableAttribute('status', ..., $ttl);
Cache Inspection: Use Tinker to inspect cached attributes:
php artisan tinker
>>> $user = App\Models\User::find(1);
>>> Cache::get("user.{$user->id}.full_name");
Logging Cache Hits/Misses: Enable debug mode in config:
'debug' => env('CACHABLE_ATTRIBUTES_DEBUG', false),
Check logs for:
[CachableAttributes] Cache hit for user.1.full_name
[CachableAttributes] Cache miss for user.1.formatted_address
Clear All Caches:
php artisan cache:clear
php artisan view:clear
Check Cache Events:
Listen for cache events in EventServiceProvider:
protected $listen = [
'Astrotomic\CachableAttributes\Events\AttributeCached' => [
\App\Listeners\LogCachedAttribute::class,
],
];
Custom Cache Drivers:
Extend the Astrotomic\CachableAttributes\Cache\CacheStore interface:
class CustomCacheStore implements CacheStore
{
public function get($key)
{
// Custom logic
}
public function put($key, $value, $ttl = null)
{
// Custom logic
}
public function forget($key)
{
// Custom logic
}
}
Register in config/cachable-attributes.php:
'store' => \App\Services\CustomCacheStore::class,
Attribute Events: Listen for cache-related events:
// In EventServiceProvider
protected $listen = [
'Astrotomic\CachableAttributes\Events\AttributeCached' => [
\App\Listeners\AnalyzeCachePerformance::class,
],
'Astrotomic\CachableAttributes\Events\AttributeInvalidated' => [
\App\Listeners\NotifyCacheInvalidation::class,
],
];
Dynamic Attribute Names: Generate cache keys dynamically:
public function getDynamicAttribute($query)
{
$key = "user.{$this->id}.{$query}";
return $this->cachableAttribute($key, function () use ($query) {
return $this->computeDynamic($query
How can I help you explore Laravel packages today?