getgrav/grav · error · InvalidArgumentException

Cache values must be array or Traversable, "%s" given

Error message

Cache values must be array or Traversable, "%s" given

What it means

CacheTrait::setMultiple() (PSR-16 'set multiple') requires its values argument to be an array or Traversable; anything else triggers InvalidArgumentException('Cache values must be array or Traversable, ...'). As with the other multi-ops the parameter is natively typed iterable, so the exception guards internal/legacy call paths while typical userland misuse surfaces as TypeError first.

Source

Thrown at system/src/Grav/Framework/Cache/CacheTrait.php:187

            }
        }

        return $values;
    }

    /**
     * @param iterable $values
     * @param null|int|DateInterval $ttl
     * @return bool
     * @throws InvalidArgumentException
     */
    public function setMultiple(iterable $values, DateInterval|int|null $ttl = null): bool
    {
        if ($values instanceof Traversable) {
            $values = iterator_to_array($values, true);
        } elseif (!is_array($values)) {
            $isObject = is_object($values);
            throw new InvalidArgumentException(
                sprintf(
                    'Cache values must be array or Traversable, "%s" given',
                    $isObject ? $values::class : gettype($values)
                )
            );
        }

        $keys = array_keys($values);

        if (empty($keys)) {
            return true;
        }

        $this->validateKeys($keys);

        $ttl = $this->convertTtl($ttl);

        // If a negative or zero TTL is provided, the item MUST be deleted from the cache.

View on GitHub (pinned to 6040efed04)

Solutions

  1. Always pass a key=>value map: $cache->setMultiple(['menu' => $menu, 'pages' => $pages], $ttl).
  2. Use json_decode($json, true) so decoded objects become arrays, which pass the check.
  3. For single values, use set($key, $value) instead of setMultiple().

Example fix

// before
$cache->setMultiple(json_decode($payload)); // stdClass -> InvalidArgumentException

// after
$cache->setMultiple((array)json_decode($payload, true));
Defensive patterns

Strategy: type-guard

Type guard

function normalizeValues(mixed $values): array
{
    if ($values instanceof \Traversable) {
        return iterator_to_array($values, true);
    }
    if ($values instanceof \stdClass) {
        return (array) $values; // json_decode without assoc=true
    }
    return is_array($values) ? $values : throw new \InvalidArgumentException('values must be iterable');
}

$cache->setMultiple(normalizeValues($values), $ttl);

Prevention

When it happens

Trigger: Calling $cache->setMultiple('serialized-string-of-values'); passing a generator-returning false/early-return value (null) straight into setMultiple(); passing an ArrayAccess object that is not Traversable; forwarding a decoded JSON scalar (string/number) instead of the expected object-as-array.

Common situations: json_decode($json) without the second argument returning an object (stdClass is Traversable? no — stdClass is NOT Traversable, so decoding a JSON object without `true` and passing it raises this); caching a single value and forgetting key=>value pairing; API responses that are scalars rather than maps.

Related errors


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