getgrav/grav · error · InvalidArgumentException
Cache keys must be array or Traversable, "%s" given
Error message
Cache keys must be array or Traversable, "%s" given
What it means
CacheTrait::getMultiple() (PSR-16 'get multiple') requires its keys argument to be an array or Traversable. If a value of any other type reaches the check, it throws InvalidArgumentException naming the actual type given. Note the method is natively typed `iterable`, so userland calls with a scalar usually fail earlier with a TypeError; this exception is the defensive path when the check runs inside untyped/internal call paths or older-PHP code paths.
Source
Thrown at system/src/Grav/Framework/Cache/CacheTrait.php:144
*/
public function clear(): bool
{
return $this->doClear();
}
/**
* @param iterable $keys
* @param mixed|null $default
* @return iterable
* @throws InvalidArgumentException
*/
public function getMultiple(iterable $keys, mixed $default = null): iterable
{
if ($keys instanceof Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!is_array($keys)) {
$isObject = is_object($keys);
throw new InvalidArgumentException(
sprintf(
'Cache keys must be array or Traversable, "%s" given',
$isObject ? $keys::class : gettype($keys)
)
);
}
if (empty($keys)) {
return [];
}
$this->validateKeys($keys);
$keys = array_unique($keys);
$keys = array_combine($keys, $keys);
$list = $this->doGetMultiple($keys, $this->miss);
// Make sure that values are returned in the same order as the keys were given.View on GitHub (pinned to 6040efed04)
Solutions
- Wrap single keys: use $cache->getMultiple(['foo']) instead of $cache->getMultiple('foo').
- Split strings first: $cache->getMultiple(explode(',', $rawKeys)).
- Assert the shape at the boundary: is_iterable($keys) before calling, and convert Traversables to arrays if you need to count them.
Example fix
// before
$values = $cache->getMultiple('menu,pages'); // string -> InvalidArgumentException
// after
$values = $cache->getMultiple(['menu', 'pages']); Defensive patterns
Strategy: type-guard
Type guard
function normalizeKeys(mixed $keys): array
{
if ($keys instanceof \Traversable) {
return iterator_to_array($keys, false);
}
if (is_string($keys)) {
return [$keys]; // tolerate single-key misuse
}
if (!is_array($keys)) {
throw new \InvalidArgumentException('Cache keys must be iterable');
}
return $keys;
}
$values = $cache->getMultiple(normalizeKeys($keys)); Prevention
- Always pass array literals (['key1', 'key2']) to PSR-16 multi-ops.
- is_iterable() guard at API boundaries that receive keys from external sources.
- Split/join string formats explicitly instead of forwarding raw strings.
When it happens
Trigger: Calling $cache->getMultiple('key1,key2') with a comma-separated string instead of an array; passing a single key string 'foo' instead of ['foo']; passing an object that is neither Traversable nor array (e.g. ArrayAccess-only wrapper) via an internal untyped caller.
Common situations: Refactoring from get() to getMultiple() and forgetting to wrap the single key in array brackets; receiving keys from an API as a string and passing them unsplit; generics/iterable confusion when a method returns null and it is forwarded as-is.
Related errors
- Cache values must be array or Traversable, "%s" given
- The class '%s' does not implement the '%s' interface
- Cache key must be string, "%s" given
- Cache key length must be greater than zero
- Cache key length must be less than 65 characters, key had %d
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/5b207fb4986f4757.
Report an issue: GitHub.