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

Illuminate Psr Cache Bridge Laravel Package

madewithlove/illuminate-psr-cache-bridge

A lightweight adapter that exposes Laravel's Illuminate cache stores as a standard PSR-6 cache pool, so libraries depending on PSR-6 interfaces can reuse your existing Laravel cache configuration and drivers without adding new backends or duplicating setup.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require madewithlove/illuminate-psr-cache-bridge — pin at least ^1.0.3 if you're on Laravel 5.8 (1.0.3 is a compat-only patch for that version; 1.0.2 and below break there). On modern Laravel the patch is a no-op, so ^1.0 is fine.
  • Read src/ first. The package is deliberately small: a CacheItemPool implementing Psr\Cache\CacheItemPoolInterface and a CacheItem implementing Psr\Cache\CacheItemInterface. Everything the package does is a mapping between PSR-6 calls and Illuminate\Contracts\Cache\Repository (get, put, has, forget, flush, many, putMany). Check the constructor in src/CacheItemPool.php for whether it wants the repository (app('cache.store')) or the underlying store (app('cache.store')->getStore()), since that changed across versions — 1.0.3 did not touch it, so whatever the previous release expected still holds.
  • Bind the pool once in a service provider so PSR-6-oriented libraries can resolve it from the container instead of knowing about Laravel's Cache facade.
// app/Providers/AppServiceProvider.php
use MadeWithLove\IlluminatePsrCacheBridge\CacheItemPool;
use Psr\Cache\CacheItemPoolInterface;

public function register(): void
{
    $this->app->singleton(CacheItemPoolInterface::class, function ($app) {
        // Dedicated store/connection is strongly recommended; see Gotchas.
        return new CacheItemPool($app['cache']->store('redis'));
    });
}
  • First realistic use case: a third-party library that type-hints Psr\Cache\CacheItemPoolInterface (HTTP client cache middleware, image/thumbnail resolvers, feed readers, API SDKs). Instead of adding a second cache backend, you hand it Laravel's already-configured Redis/database/file store.
$item = $pool->getItem('user.42.profile');

if (! $item->isHit()) {
    $item->set($expensiveProfile);
    $item->expiresAfter(3600); // PSR-6: seconds
    $pool->save($item);
}

return $item->get();

Implementation Patterns

Container binding with a named pool per consumer. Different PSR-6 consumers rarely want to share a namespace or a clear() scope. Register a small factory or contextual binding per integration:

$this->app->when(ImageResolver::class)
    ->needs(CacheItemPoolInterface::class)
    ->give(fn ($app) => new CacheItemPool($app['cache']->store('redis')));

$this->app->when(FeedReader::class)
    ->needs(CacheItemPoolInterface::class)
    ->give(fn ($app) => new CacheItemPool($app['cache']->store('file')));

Bulk operations. getItems([...]) and deleteItems([...]) map to Laravel's many()/putMany()/forget() loops. Use them when a consumer hydrates a list of IDs; it avoids N round-trips to Redis. Verify in the source whether the bridge calls many() or loops get(); if it loops, batch at your call site instead.

Deferred writes for request-scoped batching. saveDeferred() + commit() is useful when a consumer computes several items and you want to limit writes:

foreach ($rows as $row) {
    $item = $pool->getItem("row.{$row->id}");
    $item->set($row);
    $item->expiresAfter(600);
    $pool->saveDeferred($item);
}

$pool->commit(); // flush the in-memory deferred queue

Deferred items live only in the pool instance, so treat commit() as mandatory (e.g., in a terminate() hook or at the end of a job).

Testing. You don't need a full Laravel app; construct the repository directly and use ArrayStore:

$pool = new CacheItemPool(new Illuminate\Cache\Repository(new Illuminate\Cache\ArrayStore));

Create a fresh pool per test and avoid binding the singleton in tests, or state leaks between tests.

Decorator for observability. The pool is an interface implementation, so wrap it rather than editing the package:

final class LoggingPool implements CacheItemPoolInterface
{
    public function __construct(private CacheItemPoolInterface $inner, private LoggerInterface $log) {}
    public function getItem(string $key): CacheItemInterface {
        $this->log->debug('psr6.get', ['key' => $key]);
        return $this->inner->getItem($key);
    }
    // ...delegate the rest
}

Coexistence with normal Laravel caching. This is a bridge, not a replacement. Keep using Cache::remember()/Cache::tags() in application code and reserve the PSR-6 pool for libraries that demand it. Just isolate the underlying store.

Gotchas and Tips

  • Version pinning and the 1.0.3 release. 1.0.3 is a patch that only fixes Laravel 5.8 compatibility — no API, no behaviour change on any other version. Practical consequences: (a) if you're on 5.8, require ^1.0.3, since ^1.0.0 can resolve to a broken 1.0.2; (b) if you're on a modern Laravel, this release changes nothing you should care about, but it does confirm the maintainers will still cut a compat patch. Treat the package as feature-frozen, not abandoned.

  • TTL units. PSR-6 uses seconds; Laravel's put() historically uses minutes. If the bridge converts (e.g., ceil($seconds / 60) or $seconds / 60), sub-minute TTLs may be rounded or lost. Test expiresAfter(30) explicitly against the store you're using; Redis, file, and database drivers round differently. expiresAt(DateTimeInterface) also needs converting to a TTL or absolute timestamp, so check the source for how absolute expirations are handled.

  • clear() is flush() and it is not namespaced. On a shared store this wipes application cache entries the PSR-6 consumer never created. Always give the pool a dedicated cache store/connection, or accept that clear() is a broad hammer.

  • Key validation vs. Laravel keys. PSR-6 reserves {}()/\@: and requires support for A-Za-z0-9_. up to 64 chars. Many PSR-6 consumers generate keys containing : (e.g., namespace:key). If the bridge validates strictly, you'll get InvalidArgumentException; if it doesn't, you'll silently persist keys that a future version may reject. Sanitize keys at your boundary (str_replace(':', '.', $key) or hash long keys) and keep keys under 64 chars.

  • null values vs. cache misses. PSR-6 distinguishes a stored null from a miss via isHit(). Laravel's has() on some stores is implemented as get() !== null, which cannot see a stored null. If your consumer caches nullable results, store a wrapper array (['v' => $value]) rather than null.

  • Deferred items are process-local. saveDeferred() does not persist until commit(). In a queue worker, a failed job before commit() loses all deferred writes silently. Commit before returning, not in a destructor.

  • No tags, no atomic increments via PSR-6. Cache::tags() and Cache::increment()/decrement() have no PSR-6 equivalent and are not exposed. If the consumer needs tag invalidation, use separate pools/stores and clear the whole pool, or use Laravel's API directly for that use case.

  • expiresAfter(0) / negative TTLs. PSR-6 treats this as immediately expired. Laravel's put($key, $value, 0) has meant "forever" in some versions and "forget" in others. Verify the bridge's branch for <= 0; if in doubt, deleteItem() and don't call save().

  • Serialization. Most stores serialize values with PHP's serialize(). Objects must be serializable, and large payloads cost CPU and memory on every getItem(). Don't cache closures, PDO instances, or resources. If you cache Eloquent models, cache IDs/arrays and rehydrate, or be deliberate about serialization overhead.

  • Version and maintenance risk. The 1.0.x line is a thin adapter over the PSR-6 interfaces, which are themselves stable since 2015 — so the blast radius of a stale release is small. The real risk is the illuminate/* constraint, not the bridge logic: verify Composer resolved a version whose illuminate/cache (or illuminate/contracts) range actually includes your framework version, and if it didn't, prefer a composer why-not check before assuming the bridge is wired correctly. Also note that Laravel 8+ implements PSR-16 (Psr\SimpleCache\CacheInterface) natively, not PSR-6 — if the consumer accepts PSR-16, you can skip this bridge entirely and inject app('cache.store').

  • Debugging tips. Log the exact key on every getItem when you see unexpected misses; PSR-6 consumers often prefix keys differently than you expect. If isHit() is false immediately after save(), check (1) TTL rounding to zero minutes, (2) key sanitization mismatch, and (3) whether the store you passed is actually the one resolved by the consumer's container binding. Use app('cache')->store('array') for a quick in-memory reproduction outside the app.

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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json