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

Do File Cache Psr 6 Laravel Package

jord-jd/do-file-cache-psr-6

PSR-6 cache adapter for DO File Cache, letting you use the PSR-6 caching interface with a simple file-based backend. Install via Composer: composer require jord-jd/do-file-cache-psr-6.

View on GitHub
Deep Wiki
Context7

Getting Started

Install:

composer require jord-jd/do-file-cache-psr-6

That pulls jord-jd/do-file-cache transitively. There is no do/file-cache SDK, no DigitalOcean API credentials, and no Spaces integration — despite what several generated write-ups about this package claim. It is a small, pure-PHP file cache with a PSR-6 wrapper on top.

Look here first, in this order:

  1. vendor/jord-jd/do-file-cache-psr-6/src/ — it's a handful of classes. Read them all in ten minutes; the README is basically just the Composer line, and there is no Laravel service provider, no facade, no auto-discovery, and no 'driver' => 'do_file_cache' entry for config/cache.php. Treat any snippet showing otherwise as fabricated.
  2. vendor/jord-jd/do-file-cache/README.md — this is where the real configuration lives (cache directory, file extension, lock mode, permissions). The PSR-6 package just constructs that underlying cache object for you.
  3. The PSR-6 spec for getItem/getItems/saveDeferred/commit semantics, since you'll be calling the interface directly rather than through Laravel's facade.

Register a singleton (the adapter has no container wiring of its own):

// app/Providers/AppServiceProvider.php
use Psr\Cache\CacheItemPoolInterface;

$this->app->singleton(CacheItemPoolInterface::class, function ($app) {
    $cache = new \JordJD\DOFileCache\DOFileCache([
        'cacheDir'             => storage_path('app/do-cache'),
        'cacheFileExtension'   => 'cache',
        'initialCachePermissions' => 0775,
    ]);

    return new \JordJD\DOFileCachePSR6\CacheItemPool($cache); // check src/ for the exact class name
});

First use case — cache an expensive read. This is the sweet spot: rendered Blade fragments, third-party API payloads, query result arrays, sitemap/feed data, anything computed once and read hundreds of times per deploy.

$pool = app(CacheItemPoolInterface::class);

$item = $pool->getItem('reports.daily.'.now()->toDateString());

if (! $item->isHit()) {
    $item->set(Report::dailyTotals());
    $item->expiresAfter(3600 * 6);
    $pool->save($item);
}

return $item->get();

Implementation Patterns

Wrap it once, use it everywhere. Nobody wants getItem/isHit/save boilerplate scattered across controllers. A tiny helper keeps call sites readable and gives you one place to add metrics later:

final class FileCache
{
    public function __construct(private CacheItemPoolInterface $pool) {}

    public function remember(string $key, int $ttl, Closure $callback): mixed
    {
        $item = $this->pool->getItem($key);
        if ($item->isHit()) {
            return $item->get();
        }

        $value = $callback();
        $item->set($value)->expiresAfter($ttl);
        $this->pool->save($item);

        return $value;
    }
}

Keep it off the Cache facade, on purpose. Laravel's Repository is PSR-16 and its Store contract is Laravel-specific; PSR-6 is a different interface and Laravel does not consume it natively. Two workable options:

  • Direct injection (recommended) — inject CacheItemPoolInterface (or the wrapper above) into the services that need disk-backed persistence, and leave Cache:: pointed at your default store. Two caches coexisting is normal and costs nothing.
  • Bridge to the facade — write a ~100-line class implementing Illuminate\Contracts\Cache\Store that delegates to the pool, then register it as a custom store. Worth doing only if you need Cache::store('do_file')->remember() ergonomics across a large legacy codebase. Watch the forever() and getPayload() methods; the semantics don't map cleanly.

Prefix keys by domain and deploy. Because clear() nukes the entire directory, a shared pool means one subsystem's flush kills another's data. Namespace defensively and version your keys so a schema change self-invalidates:

$key = 'v3.users.'.md5($userId);   // bump v3 → all old entries become misses

Batch when warming. For seeding caches in a queue job or deploy hook, prefer getItems() / saveDeferred() / commit() over loops of single calls — one round of directory operations instead of N.

$items = $pool->getItems(array_map(fn ($id) => "product.$id", $ids));
foreach ($items as $key => $item) {
    if (! $item->isHit()) {
        $item->set($repo->find($key))->expiresAfter(86400);
        $pool->saveDeferred($item);
    }
}
$pool->commit();   // forget this and nothing is written

Own the invalidation lifecycle. php artisan cache:clear, optimize:clear, and cache:forget() will not touch this pool. Whatever writes here also needs a route to clean it:

// app/Console/Commands/FlushDoCache.php
Artisan::command('do-cache:flush {--prefix=}', function () {
    if (! $this->option('prefix')) {
        return app(CacheItemPoolInterface::class)->clear();
    }
    // prefix-scoped flush: walk the dir yourself, the adapter cannot do this
});

Hook that into your deploy script and into a listener on any model event that invalidates derived data.

Testing. Rebinding the container to an in-memory PSR-6 pool keeps test suites fast and hermetic:

$this->app->instance(CacheItemPoolInterface::class, new \Symfony\Component\Cache\Adapter\ArrayAdapter());

That swap is the single biggest argument for injecting the interface rather than the concrete adapter.

Gotchas and Tips

Key validation will bite you first. PSR-6 forbids { } ( ) / \ @ : and reserves empty keys; keys are also capped at 64 characters in practice. Raw URLs, emails, file paths, and JSON blobs all fail. Hash them — md5() of the source string is the standard fix — and keep a human-readable prefix in front for debuggability.

You cannot cache null. A hit is a hit only if the item exists; getItem()->get() returns null for a miss and for a cached null. If "not found" is a legitimate cached answer (very common with API lookups), wrap the value: ['found' => false] or a sentinel object.

No tags. PSR-6 core has no tag support, and this adapter adds none — the tag examples floating around are from Symfony's TagAwareAdapter. Do group invalidation with key versioning (v3.users.*) or a manifest key holding the list of child keys.

commit() is not optional. Deferred saves are dropped silently if you forget it. It's the most common "why is my cache always cold" bug with this package.

Directory permissions across processes. CLI (deploy hooks, cron) and php-fpm usually run as different users. The first process to create the directory owns it, and the other silently fails to write. Set initialCachePermissions explicitly and put the cache outside storage/framework/cache — that directory is Laravel's own FileStore playground and cache:clear deletes files there by prefix.

Don't put it on shared/network storage. Locking is file-based; NFS or a container volume that isn't local will produce flaky reads and slow clear(). And it is per-machine — horizontally scaled apps will have inconsistent caches with no coordination. Fine for read-through of derivable data; never use it for anything correctness-critical.

Serialization drift. Values are serialized PHP objects. Rename a class or change a property shape and stale entries unserialize into something your code no longer expects. Version your key prefix on every deploy that changes cached payload shapes, or keep cached values as plain arrays.

Debugging recipe: ls -la storage/app/do-cache and look at file mtimes to confirm writes are landing; hasItem() in php artisan tinker to confirm reads; an InvalidArgumentException from getItem() is almost always a bad key character, not a missing entry. Wrap the pool in a decorator implementing CacheItemPoolInterface if you want hit/miss logging — that's the cleanest extension point, since the adapter itself is only a thin shim and shouldn't be forked.

Pin the major and read upgrade notes. v2.0.0 moved to a new namespace; check every use statement when bumping. With 8 stars, zero dependents, and a docs-only 3.0.1 release, expect to be your own support line — budget an afternoon to read the source rather than trusting blog posts about it.

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.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table