cache/psr-6-doctrine-bridge
Adapts any PSR-6 cache pool to Doctrine's legacy Cache interface. Install via Composer, then use contains, fetch, save and delete as usual; falls back to SHA-256 keys when the pool rejects a key. Includes getCachePool access.
This package is a small adapter: it implements Doctrine’s legacy Doctrine\Common\Cache\Cache interface on top of any PSR-6 CacheItemPoolInterface. In a Laravel app it matters only if you have Doctrine ORM components that still expect Doctrine’s cache API but you want to back them with a PSR-6 pool. Pure Eloquent apps should skip it.
Minimal path:
composer require cache/psr-6-doctrine-bridge symfony/cache
use Cache\Bridge\Doctrine\DoctrineCacheBridge;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
$bridge = new DoctrineCacheBridge(new ArrayAdapter());
$bridge->save('smoke', ['ok' => true], 60);
var_dump($bridge->contains('smoke')); // true
var_dump($bridge->fetch('smoke')); // ['ok' => true]
$bridge->delete('smoke');
use Symfony\Component\Cache\Adapter\RedisAdapter;
$pool = new RedisAdapter(
Redis::connection()->client(),
'doctrine', // namespace/prefix
0
);
$bridge = new DoctrineCacheBridge($pool);
Configuration where the EntityManager is built, before the EM is used:$config = $em->getConfiguration();
$config->setMetadataCacheImpl($bridge);
$config->setQueryCacheImpl($bridge);
// $config->setResultCacheImpl($bridge); // only when you own invalidation
Where to look first: the README for the public methods, the DoctrineCacheBridge class source for exact key/TTL behavior, and composer.json for the supported doctrine/cache and PSR versions. First real use case: move Doctrine metadata caching to Redis while keeping Doctrine ORM 2.x code unchanged.
Bind the bridge as a singleton. In Laravel, keep one pool/bridge per Doctrine cache purpose:
// AppServiceProvider::register()
$this->app->singleton(DoctrineCacheBridge::class, function () {
$pool = new \Symfony\Component\Cache\Adapter\RedisAdapter(
\Redis::connection()->client(),
'doctrine'
);
return new DoctrineCacheBridge($pool);
});
Then resolve it in the same factory/service provider that builds the Doctrine Configuration. If your Doctrine integration only accepts string cache driver names, register a custom driver or extend its cache manager; setting setMetadataCacheImpl() after the EM has already created its metadata factory may be too late.
Reuse Laravel’s cache stores via PSR-16. Laravel’s cache repository implements PSR-16. Symfony can wrap a PSR-16 cache as a PSR-6 pool:
use Symfony\Component\Cache\Adapter\Psr16Adapter;
$pool = new Psr16Adapter(Cache::store('redis'));
$bridge = new DoctrineCacheBridge($pool);
This is convenient when you want Doctrine metadata/query cache to share Laravel’s cache configuration. It is a thinner abstraction than using a native PSR-6 pool, so validate TTLs and key handling with your actual driver.
Prefer a dedicated pool for Doctrine. The bridge does not expose PSR-6 namespaces/tags, so mixing Laravel application keys and Doctrine keys in one pool makes debugging and invalidation harder. Use a separate Redis DB/prefix or a separate pool per cache type:
Testing pattern. Use ArrayAdapter in unit tests and assert the Doctrine-facing behavior:
$bridge = new DoctrineCacheBridge(new ArrayAdapter());
$this->assertFalse($bridge->fetch('missing'));
$bridge->save('key', ['value'], 60);
$this->assertTrue($bridge->contains('key'));
$this->assertSame(['value'], $bridge->fetch('key'));
For integration tests, use a real Redis pool and load an entity twice; the second load should hit metadata cache. Clear the pool between test cases.
Deploy/clear pattern. Because the bridge does not implement Doctrine’s ClearableCache, CLI commands that call deleteAll() may not clear the pool. Either clear the dedicated pool directly ($bridge->getPool()->clear()) during deploy, or create a small wrapper that implements ClearableCache and delegates to clear(). Keep this pool dedicated if you do that.
Extension point. getPool() gives you the underlying PSR-6 pool for health checks, metrics, or direct clearing. Use it for operational tooling, but avoid writing application cache entries into the same key space as Doctrine.
Check your Doctrine version first. This bridge is for code that still expects Doctrine\Common\Cache\Cache. Doctrine ORM 3.x and newer Doctrine cache integrations may use PSR-6 directly, making the bridge unnecessary. Pin and test doctrine/orm, doctrine/cache, and the bridge together; doctrine/cache 2.x added stricter signatures, so an old bridge release can break on return types.
PSR-6 reserved characters are the first failure point. PSR-6 reserves {}()/\@: in keys. Doctrine metadata keys are often fully qualified class names containing \. Test early:
$bridge->save('App\\Entity\\User', ['meta'], 60);
If your pool throws InvalidArgumentException, the bridge either needs to normalize keys or you need a pool decorator that does. The bridge is small; forking it to replace reserved characters in fetch/contains/save/delete is often the fastest fix.
TTL 0 means “forever” in Doctrine. Verify that the bridge does not call expiresAfter(0), which some PSR-6 pools treat as immediate expiration. A quick integration test against your production pool is worthwhile.
fetch() returns false on a miss. If you cache the literal value false, you cannot distinguish a hit from a miss through the Doctrine interface. Doctrine metadata/query cache values are usually arrays/objects, so this is rarely an issue.
Namespaces and stats are not reliable. setNamespace() may be a no-op and getStats() may return empty/null. Do not build operational dashboards on the bridge’s stats. Use Redis INFO, Symfony Cache metrics, or a pool decorator that logs hits/misses.
No tags. PSR-6 tags are not part of the spec and the bridge does not expose them. Doctrine result cache invalidation must be manual (delete($key)), or you must use a tag-aware pool outside the bridge and manage tags in your own code.
No multi-get/multi-put. The bridge implements the basic Cache interface, not Doctrine’s MultiGetCache/MultiPutCache. Doctrine metadata loading may do contains() followed by fetch(), which is two cache round trips per class. That is usually fine for metadata, but benchmark if you are replacing a native multi-get cache in a hot path.
Serialization matters. Doctrine caches metadata objects and query parsing results. Use a pool that serializes values correctly for production (Redis, filesystem, etc.). ArrayAdapter is only for tests or short-lived processes; it will grow unbounded in long-running workers.
Clear old cache after schema changes. Metadata cache contains mapping information. After migrations or entity mapping changes, clear the dedicated Doctrine pool before warming. If you reuse a shared Laravel cache store, make sure the prefix or database is distinct enough that a broad Cache::flush() does not wipe unrelated application cache.
How can I help you explore Laravel packages today?