fm/cache-bundle
fm/cache-bundle is a caching package for Laravel and PHP applications that bundles cache configuration, stores, and helpers into one reusable component, making it easier to set up and manage application caching with less boilerplate and more consistency across projects.
Read this first: it is not a Laravel package. fm/cache-bundle is a Symfony2 bundle (Financial Media's Doctrine ORM caching layer), last released 2013-10-25, archived, zero dependents. There is no ServiceProvider, no facade, no config/cache-bundle.php, no publishes() hooks. composer require fm/cache-bundle in a modern Laravel app will fail on symfony/* and doctrine/* version constraints before you ever write a line of code.
So the realistic "first steps" are:
DependencyInjection/ extension and the Doctrine event subscriber/listener classes — that is where the entire caching behaviour lives. Everything else is wiring.(entity class, primary key, version), and evict on postPersist / postUpdate / postRemove. That idea ports to Laravel cleanly; the implementation does not.User::find($id) on every request:// app/Cache/KeyBuilder.php
final class KeyBuilder
{
public static function entity(string $class, int|string $id, int $version = 1): string
{
return sprintf('%s:%s:v%d', class_basename($class), $id, $version);
}
}
// app/Models/User.php
public static function cached(int $id): ?self
{
return Cache::remember(
KeyBuilder::entity(static::class, $id),
now()->addMinutes(15),
fn () => static::find($id)
);
}
That's ~10 lines and reproduces 90% of what the bundle was doing on a 2013 Symfony2 stack.
If you genuinely need to run the original (e.g. auditing legacy behaviour), do it in a disposable Symfony 2.8 container, never inside the Laravel app.
The bundle operates as a Doctrine event subscriber + annotation-configured entity cache. The patterns worth translating:
1. Read-through at the repository boundary. In Symfony the listener intercepts hydration. In Laravel you wrap the query:
public function findCached(int $id): ?User
{
$key = KeyBuilder::entity(User::class, $id);
return Cache::remember($key, $this->ttl, fn () => $this->find($id));
}
2. Model-event invalidation — the Laravel equivalent of postUpdate/postRemove:
protected static function booted(): void
{
static::saved(fn (self $m) => Cache::forget(KeyBuilder::entity(self::class, $m->getKey())));
static::deleted(fn (self $m) => Cache::forget(KeyBuilder::entity(self::class, $m->getKey())));
}
Note saved covers both insert and update — the bundle's separate persist/update handlers collapse neatly here.
3. Query/collection caching with version stamps. Rather than deleting list keys, bump a version counter:
public function paginateCached(int $page): LengthAwarePaginator
{
$version = Cache::get('users:version', 1);
return Cache::remember("users:list:{$version}:{$page}", 600, fn () => $this->paginate(20));
}
// invalidation: Cache::increment('users:version');
This is the pattern the bundle approximated with Doctrine's second-level cache regions — regions map almost 1:1 onto Laravel cache tags (Redis/Memcached only).
4. Stampede protection. The bundle predates this entirely. Wrap cold reads in Cache::lock() when a hot key expires under load.
5. Keep key construction in one class. If you end up with keys scattered across models, controllers and jobs, invalidation becomes a archaeology exercise. Centralise in KeyBuilder and unit-test it.
Integration tips:
Cache::tags(['users'])->flush()) only on taggable stores — the file driver silently throws.public int $cacheTtl = 900;) so policy lives next to the data.Cache::spy() + shouldHaveReceived('put') rather than hitting a real store.UnitOfWork, change sets and second-level cache regions. Eloquent has no change-set concept for cache invalidation — saved fires even when nothing changed, and updated doesn't fire on no-op saves. Decide deliberately: invalidate eagerly (safe, slightly wasteful) or compare getDirty() (tighter, easy to get wrong).doctrine/annotations, which is deprecated and removed from the Doctrine ecosystem; PHP 8 attributes would be required. Another reason nothing here is drop-in.postUpdate only fires on an actual flush change set in the original — a classic source of stale data if you assume "save = invalidate". Your Laravel port should not inherit that ambiguity.remember callback temporarily. Cache::has() before and after a request is a cheap smoke test.Cache manager and your KeyBuilder.genealabs/laravel-model-caching for transparent model-level caching, spatie/laravel-responsecache for HTTP-level, or the ~50 lines above with Cache::remember + booted() hooks. Given zero stars, zero dependents and a decade of dormancy, the bundle is a historical reference for what to cache, never how to do it today.How can I help you explore Laravel packages today?