getgrav/grav · error · InvalidArgumentException

At least one cache must be specified

Error message

At least one cache must be specified

What it means

ChainCache composes several cache backends into one read-through chain and mandates a non-empty list: its constructor throws InvalidArgumentException('At least one cache must be specified') when the $caches array is empty. An empty chain has no semantics (nothing to read from or write to), so Grav refuses to construct it.

Source

Thrown at system/src/Grav/Framework/Cache/Adapter/ChainCache.php:47

    /** @var int */
    protected $count;

    /**
     * Chain Cache constructor.
     * @param array $caches
     * @param null|int|DateInterval $defaultLifetime
     * @throws InvalidArgumentException
     */
    public function __construct(array $caches, $defaultLifetime = null)
    {
        try {
            parent::__construct('', $defaultLifetime);
        } catch (\Psr\SimpleCache\InvalidArgumentException $e) {
            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
        }

        if (!$caches) {
            throw new InvalidArgumentException('At least one cache must be specified');
        }

        foreach ($caches as $cache) {
            if (!$cache instanceof CacheInterface) {
                throw new InvalidArgumentException(
                    sprintf(
                        "The class '%s' does not implement the '%s' interface",
                        $cache::class,
                        CacheInterface::class
                    )
                );
            }
        }

        $this->caches = array_values($caches);
        $this->count = count($caches);
    }

View on GitHub (pinned to 6040efed04)

Solutions

  1. Always pass at least one adapter: `new ChainCache([$primary])` or `[$fast, $slow]`.
  2. If the chain is config-driven, fall back to a always-available adapter (e.g. Grav\Framework\Cache\Adapter\FileCache) when the filtered list ends up empty.
  3. Fix the environment so the intended adapters survive filtering: install php-redis/php-memcached or correct the driver names in cache configuration.

Example fix

// before
$adapters = array_filter([$redis ?? null, $memcached ?? null]); // empty when ext missing
$cache = new ChainCache($adapters); // throws

// after
$adapters = $adapters ?: [new FileCache('cache://runtime')];
$cache = new ChainCache($adapters);
Defensive patterns

Strategy: validation

Validate before calling

$adapters = array_values(array_filter($candidateAdapters));
if ($adapters === []) {
    $adapters = [new \Grav\Framework\Cache\Adapter\FileCache('cache://runtime')];
}
$chain = new \Grav\Framework\Cache\Adapter\ChainCache($adapters);

Prevention

When it happens

Trigger: Constructing `new ChainCache([])` directly; building the chain from configuration where every configured adapter was filtered out (e.g. driver availability check removed Redis/Memcached because the extension is missing) leaving zero entries; dynamic code assembling adapters based on runtime conditions that all evaluate false.

Common situations: A cache config listing only non-PHP-extension drivers (redis, memcached) on a host lacking those extensions, so the chain ends up empty; conditional adapter registration (staging vs production) with a typo making no branch match; test fixtures creating ChainCache from an empty provider list.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/3f6dbebcfedf6ac2. Report an issue: GitHub.