doctrine/orm · error · InvalidArgumentException

Unable to use access strategy type of [%s] without a Concurr

Error message

Unable to use access strategy type of [%s] without a ConcurrentRegion

What it means

InvalidArgumentException from DefaultCacheFactory::buildCachedEntityPersister() (src/Cache/DefaultCacheFactory.php:82). An entity is mapped with CACHE_USAGE_READ_WRITE (the `#[Cache(usage: 'READ_WRITE')]` strategy), which needs a lockable ConcurrentRegion to keep cached state consistent across processes — but the region resolved for that entity's cache region name is a plain Region instance.

Source

Thrown at src/Cache/DefaultCacheFactory.php:82

    }

    public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister
    {
        assert($metadata->cache !== null);
        $region = $this->getRegion($metadata->cache);
        $usage  = $metadata->cache['usage'];

        if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
            return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata);
        }

        if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
            return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
        }

        if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
            if (! $region instanceof ConcurrentRegion) {
                throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage));
            }

            return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
        }

        throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
    }

    public function buildCachedCollectionPersister(
        EntityManagerInterface $em,
        CollectionPersister $persister,
        AssociationMapping $mapping,
    ): CachedCollectionPersister {
        assert(isset($mapping->cache));
        $usage  = $mapping->cache['usage'];
        $region = $this->getRegion($mapping->cache);

        if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Register a ConcurrentRegion (e.g. FileLockRegion wrapping your region) for that region name via $cacheFactory->setRegion(new FileLockRegion($region, $dir, $lockLifetime)).
  2. Or set DefaultCacheFactory::setFileLockRegionDirectory($dir) so getRegion() wraps READ_WRITE regions in a FileLockRegion automatically, and make sure the region name isn't pre-registered as a plain region.
  3. Downgrade the entity to 'NONSTRICT_READ_WRITE' if strict locking is not required — it works with plain regions.
  4. Give READ_WRITE entities their own region name so a previously-built non-concurrent region cannot be reused.

Example fix

// before: READ_WRITE entity resolves to a plain DefaultRegion
$factory->setRegion(new DefaultRegion('my_entity_region', $pool, 3600));
// #[Cache(usage: 'READ_WRITE', region: 'my_entity_region')] on the entity -> InvalidArgumentException

// after: wrap it in a FileLockRegion (a ConcurrentRegion)
$factory->setRegion(
    new FileLockRegion(
        new DefaultRegion('my_entity_region', $pool, 3600),
        '/var/lock/doctrine/my_entity_region',
        60,
    )
);
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling READ_WRITE, make sure the region is concurrent
$factory = new DefaultCacheFactory($regionsConfig, $pool);
if ($usesReadWrite) {
    $factory->setRegion(new FileLockRegion(
        new DefaultRegion('my_region', $pool, 3600),
        $lockDir . '/my_region',
        60,
    ));
}

Prevention

When it happens

Trigger: A custom region registered via DefaultCacheFactory::setRegion(new DefaultRegion(...)) under the same region name the entity uses; or the region for that name was already created (and memoized in $this->regions) as a non-concurrent DefaultRegion because another entity with the same region name used READ_ONLY/NONSTRICT_READ_WRITE first. Note: if no directory was configured at all you get the separate LogicException about setFileLockRegionDirectory() first — this exception means a region exists but has the wrong type.

Common situations: Several entities sharing one region name while mixing cache usage strategies; swapping in a custom region implementation (e.g. Redis-backed DefaultRegion) and forgetting READ_WRITE entities need a ConcurrentRegion decorator; upgrading second-level cache config where regions were previously per-usage.

Related errors


AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21). Data as JSON: /api/errors/91bca6fc82435c16. Report an issue: GitHub.