ahmedzidan/laravel-cacheable
Laravel Cacheable adds automatic caching to your Eloquent models, so frequent queries are served from cache and invalidated on create, update, or delete. Drop in the trait, configure a TTL, and cut database load with almost no code changes.
Before you install anything, verify compatibility. This package is archived and marked abandoned. Its composer.json constraints target older Laravel versions, so on Laravel 9+ you may need to fork it, relax the constraint, or use --ignore-platform-reqs only as a short-term experiment. Treat it as a legacy utility: fine for an existing app already on it, risky for a new build.
Minimal setup:
composer require rinvex/laravel-cacheable
php artisan vendor:publish --provider="Rinvex\Cacheable\Providers\CacheableServiceProvider"
The published config/cacheable.php controls the cache store, TTL, and key prefix. Point it at Redis or Memcached if you want tag-based invalidation:
// config/cacheable.php
return [
'store' => env('CACHE_DRIVER', 'redis'),
'ttl' => 60, // confirm units in your version — commonly minutes
'prefix' => 'cacheable.',
];
Then opt individual models in:
use Rinvex\Cacheable\CacheableEloquent;
class Category extends Model
{
use CacheableEloquent;
}
Where to look first: the query builder the trait swaps in. Cacheable works by intercepting Eloquent reads (find, first, where(...)->get(), etc.) and replaying them through the cache, then flushing on model saved/deleted events. Once that mental model clicks, the rest is configuration and edge cases.
First use case: a read-heavy lookup table — categories, countries, settings, plan definitions. Something queried on nearly every request and written rarely.
$categories = Category::where('active', true)->orderBy('name')->get(); // cached
$category = Category::findOrFail($id); // cached
If the second call in a request is measurably faster and your database query count drops, it is wired correctly.
Be selective. Add the trait only to models with a high read-to-write ratio. Applying it to a busy transactional model (orders, ledger entries) turns every write into a cache flush and gains little.
Pair every “dirty” write path with an explicit flush. Model events cover save(), create(), delete(), but not everything:
Category::where('parent_id', $id)->update(['active' => false]);
Category::flushCache(); // call this yourself
Wrap bulk operations in a small service method that flushes after the write, rather than scattering flushCache() calls through controllers.
Use tags when the store supports them. Redis and Memcached support tags; file and database stores do not. Without tags, flushing falls back to prefix-based invalidation, which is coarser and can be slower on large caches. If you need per-model or per-tenant flushing, use a tag-capable store.
Namespace by tenant or environment. Set the prefix from config to include tenant and app environment so two tenants or a staging/dev pair never collide:
'prefix' => 'cacheable.'.tenant()->id.'.'.app()->environment().'.',
Keep cached queries deterministic. Avoid embedding now(), random values, or request-scoped state inside cached query scopes. If a query varies per user, ensure that context is part of the SQL bindings (e.g. a where user_id = ? global scope) so keys differ, or bypass the cache entirely.
Warm after deploy. After schema or seed changes, run the hot reads once or flush and let the cache rebuild:
Category::flushCache();
Testing pattern: use the array cache store in phpunit.xml, and flush in setUp() so tests do not observe each other’s cached models:
protected function setUp(): void
{
parent::setUp();
Cache::flush();
}
Cache::remember(), a repository layer, or a maintained alternative.->update(), ->increment(), ->decrement(), ->insert(), DB::table(...), and raw SQL do not fire model events. Cached rows go stale silently. Audit these paths and flush explicitly.Post::with('author')->find($id) and the author’s name changes, the Post cache is unaffected. Either avoid eager-loading volatile relations in cached queries or flush related models on write.DB::afterCommit(fn () => Category::flushCache()).local by conditionally applying it, or switch the store to array. Inspect raw keys in Redis (KEYS 'cacheable.*') to confirm prefixing and TTLs. Add temporary logging inside your flush calls to verify they fire.flushCache() on a model to add custom invalidation (e.g. clearing a response cache alongside), or listen to saved/deleted to fan out flushes to related models. Do not rely on internals you cannot read; the package is not maintained, so anything undocumented is effectively private API.How can I help you explore Laravel packages today?