Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Caching Laravel Package

nette/caching

Nette Caching is a fast, flexible PHP caching library with memory, file, and distributed backends. It supports expiration, tags, namespaces, and dependency-based invalidation, making it easy to speed up PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Know the boundary first. In a Laravel app this is not a replacement for Illuminate\Cache. nette/caching does not implement Illuminate\Contracts\Cache\Repository, has no service provider, no facade, no CacheHit/CacheMissed events, and no Octane or lock integration. Adopt it for a specific gap (file-based tag invalidation, Cache::ITEMS dependency graphs, shared cache semantics with a Nette/Latte service, PSR-16 interop), not as "the app cache."

Minimal wiring — three lines in a service provider:

$this->app->singleton(\Nette\Caching\Cache::class, function () {
    $journal = new \Nette\Caching\Storages\SQLiteJournal(storage_path('framework/nette-journal.db'));
    $storage = new \Nette\Caching\Storages\FileStorage(storage_path('framework/nette-cache'), $journal);
    return new \Nette\Caching\Cache($storage);
});

Cache → Storage → Journal is the whole architecture. Storage is the backend (FileStorage, MemoryStorage, MemcachedStorage, SQLiteStorage, DevNullStorage); Journal is the sidecar index that makes tags, priority and dependency bookkeeping possible. If you skip the journal, tags do not work — this is the single most common "why isn't my invalidation firing" bug.

Where to look first (in order):

  1. Nette\Caching\Cache — the only class you call day to day. Read the load()/save()/capture()/bulkLoad() signatures before anything else.
  2. docs/internals.md in the repo — the maintainers explicitly flag it as the source of truth for the overloaded null, dependency desugaring, the cross-method lock, and the FileStorage file format. Read it before writing a Storage decorator.
  3. Nette\Caching\Storages\FileStorage constructor — it tells you exactly what the journal is for.

First use case worth piloting: a Blade partial or API response assembled from several expensive lookups, where you want the fragment to invalidate when any of the inputs change. Laravel's Cache::tags() cannot do this on the file or database driver, and nette/caching can via Cache::ITEMS:

$html = $cache->load("page:$slug", function () use ($repo) {
    return view('page', ['page' => $repo->find($slug)])->render();
}, [
    \Nette\Caching\Cache::ITEMS => ["page-data:$slug", "nav:$locale"],
    \Nette\Caching\Cache::EXPIRATION => '30 minutes',
]);

That is the shape of the value proposition: dependency-driven invalidation on a filesystem-backed cache.

Implementation Patterns

Memoize with load(), not with remember() muscle memory. load($key, $fallback, $dependencies) runs the fallback on a miss and writes the returned value with those dependencies automatically. If the fallback returns null, nothing is stored. On 3.2.2+ $dependencies is the third parameter of load(); on older 3.x you must call $cache->save($key, $value, $deps) yourself inside the fallback. Pin the constraint and check it.

Dependency keys you will actually use:

Key Day-to-day use
Cache::EXPIRATION '30 minutes' or seconds; computed TTLs must be positive
Cache::SLIDING true for "reset TTL on read" (sessions, hot profiles)
Cache::FILES Re-invalidate config/asset-derived values in local dev
Cache::TAGS Bulk invalidation — requires a journal
Cache::ITEMS Compose caches from other cache keys (page depends on fragments)
Cache::CONSTANTS Cheap "bump this constant to invalidate" escape hatch

Fragment caching with capture(). The Nette-idiomatic pattern, and what Latte's {cache} macro compiles down to:

if ($capture = $cache->capture("nav:$locale", [Cache::EXPIRATION => '1 hour'])) {
    try {
        echo $view->render('nav', compact('locale'));
        $capture->end();          // commits the output buffer to cache
    } catch (\Throwable $e) {
        $capture->rollback();     // added in 3.0.2; do not skip this
        throw $e;
    }
}
// on a hit, capture() already echoed the cached bytes

capture() returns null on a hit and prints the cached content, so a try { } finally { } around your echo block is mandatory — an exception without rollback() leaves the buffer open and corrupts the rest of the response. (Note: start() was renamed to capture() in 3.1.0; older snippets will not run.)

Tag-based invalidation, done deliberately. With FileStorage + SQLiteJournal, Cache::TAGS gives you real invalidation on a local-disk backend:

$cache->save("product:{$id}", $row, [
    Cache::TAGS => ["product:$id", 'catalog'],
    Cache::EXPIRATION => '1 day',
]);

// after an import:
$cache->clean([Cache::TAGS => ['catalog']]);

Use Cache::NAMESPACES the same way to carve out logical regions (clean([Cache::NAMESPACES => ['v2']])) — cheaper and safer than a full clean() sweep on a file backend.

Bulk reads for list endpoints. bulkLoad(array $keys) returns only the keys that hit, so array_diff_key($keys, $found) is your miss list. On MemcachedStorage this maps to a single multi-get; on FileStorage it is still N file stats, so it buys you code clarity, not I/O. Bulk writes landed in 3.3.0 — verify the version before relying on them.

Instrument by decorating Storage, not by forking Cache. Storage::read() returns [$data, $dependencies]; implement Nette\Caching\Storage (and Nette\Caching\BulkReader for bulk paths) as a thin wrapper around the real storage, log hits/misses/timings, and bind the decorator in the container. This is the only realistic way to get CacheHit/CacheMissed-style observability, because nette/caching emits no events of its own — if you adopt it, you own the metrics gap.

Testing. Bind DevNullStorage to assert "cache is a no-op" and MemoryStorage for assertion-friendly unit tests. But be aware: nette/caching uses wall-clock time(), so Carbon::setTestNow() / $this->travel() will not move its expiration clock. Test expiry by injecting a short TTL and actually waiting, or by reading the storage's dependency metadata directly.

Interop. If a shared library needs framework-agnostic caching, the PSR-16 adapter added in 3.3.1 is the low-friction bridge — and since Illuminate\Cache\Repository is already PSR-16 compatible, that same interface is a plausible exit strategy if you later want to drop nette/caching.

Gotchas and Tips

Put the cache directory outside the document root. Since 3.0.0 the <?php prefix was removed from cache files, so FileStorage output is plain serialized data. If the directory is web-served, cached payloads (including PII and secrets) are downloadable. In Laravel, storage_path('framework/...') is correct; public/cache is a leak.

File permissions across CLI and FPM. storage/bootstrap directories are typically written by the deploy user during php artisan and by www-data at runtime. SQLiteJournal and SQLiteStorage set permissions "like an ordinary file," so you will get intermittent write failures when the two users differ. Standardize with chmod -R g+w / a shared group, and never write the journal from both contexts in the same second — SQLite locking errors are the known symptom (fixed/optimized in 3.1.4, still visible under NFS).

The null overload. null means both "miss" and "cached null" through load(). If a legitimate null must be cached, wrap it (a sentinel object or bulkLoad()'s presence-in-array semantics). Passing null explicitly as the fallback is not the same as omitting the argument — the implementation distinguishes them.

Zero or negative expiration silently skips the write. Since 2.5.3, save() does not store data with zero/negative expiration. Guard computed TTLs: max(1, $ttl) or skip the cache call entirely, otherwise you get a cache that looks wired up but never populates.

load() takes a lock while the fallback runs. Concurrent misses on the same key serialize. That is the anti-stampede feature, but it means a slow fallback (HTTP call, big query) blocks other workers on that key. Keep fallbacks short, do not call $cache->load($sameKey) recursively inside its own fallback, and do not put queue pushes or outbound webhooks in there. On shared/NFS volumes flock is unreliable — do not deploy FileStorage on a network mount.

Callbacks and objects in dependencies must survive serialization. Cache::CALLBACKS (and Cache::FILES, Cache::TAGS with object payloads) get serialized into the journal. Closures fail outright; [$this, 'method'] serializes the whole object graph and will surprise you when that object holds a PDO handle. Prefer static callables or minimal invokable classes. If you are on the fence, use Cache::CONSTANTS or Cache::ITEMS instead.

Tags are a silent no-op without a journal. new FileStorage($dir) with the default null journal gives you expiration but no working tag/priority invalidation. Every "tags don't invalidate" report starts here. SQLiteJournal needs pdo_sqlite; it was created always and lazily checked across 2.4.x–2.5.x, so version-specific lazy-init behavior differs — always confirm the extension is loaded in the target runtime.

clean() is expensive on files. A full cache->clean() walks the directory; calling it on every deploy or every request will show up in latency graphs. Prefer tags, namespaces, or a version-in-key scheme. Similarly, FileStorage garbage collection is probabilistic on write — a low-write app accumulates files indefinitely.

Version drift bites copied code. start() → capture() (3.1.0), IStorage/IJournal/IBulkReader → Storage/Journal/BulkReader (3.1.0), NewMemcachedStorage → MemcachedStorage (3.0.0), Latte 2 support removed (3.4.0), CacheExtension now takes a complete path (3.3.0 — a stated BC break). Anything from a pre-2021 blog post will fail type checks.

MemcachedStorage wants the memcached extension, not memcache. It is a thin wrapper around a Memcached instance you construct and pass in. Under Octane the connection persists across requests — that is a win, but it also means a Memcached object that dies mid-worker stays dead; add a reconnect/health check if you go this route.

Do not run two invalidation stories. If half your app invalidates via Cache::forget() and half via $cache->clean([Cache::TAGS => ...]), you will eventually ship stale data that nobody can trace. Pick one owner per key space, document it in the service provider, and enforce it in review. This is the real cost of adopting the package in a Laravel codebase — larger than the integration code itself.

License: NOASSERTION. Not MIT, not clearly anything. Get legal review before this becomes a transitive dependency of anything you ship, and note that with zero listed dependents you are the de facto maintainer of your own integration.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/command-executor-lib
tagixo/document-builder
tagixo/page-builder
tagixo/mail-builder
tagixo/form-builder
primix/primix
tagixo/core
alesima/laravel-azure-service-bus
tomshaw/shopcart
likyly/laravel
darthsoup/shoppingcart
tdt/core
labyrinthe/connect-php
encore2/laravel-admin2
cboxdk/laravel-console-kit
shapecode/doctrine-dbal-datetimeutc
gilads-otiannoh254/inertia-protocol
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite