serbanghita/Mobile-Detect · error · CacheInvalidArgumentException

%s must be iterable.

Error message

%s must be iterable.

What it means

getMultiple(), setMultiple() or deleteMultiple() on the bundled Cache received a first argument that is not iterable (not an array and not Traversable). checkIterable() at src/Cache/Cache.php:243 formats the message with ucfirst($argName), so the thrown message actually reads 'Keys must be iterable.' or 'Values must be iterable.'. PSR-16 requires iterables for these batch methods; scalars, null, and non-Traversable objects fail this guard.

Source

Thrown at src/Cache/Cache.php:243

     */
    protected function checkTtl($ttl): int|DateInterval|null
    {
        if ($ttl !== null && !is_int($ttl) && !($ttl instanceof DateInterval)) {
            throw new CacheInvalidArgumentException('TTL must be null, int, or DateInterval.');
        }

        return $ttl;
    }

    /**
     * @param mixed $iterable
     * @return iterable<mixed>
     * @throws CacheInvalidArgumentException
     */
    protected function checkIterable($iterable, string $argName): iterable
    {
        if (!is_iterable($iterable)) {
            throw new CacheInvalidArgumentException(sprintf('%s must be iterable.', ucfirst($argName)));
        }

        return $iterable;
    }

    protected function getTTL(DateInterval|int|null $ttl): ?int
    {
        if ($ttl instanceof DateInterval) {
            return (new DateTime())->add($ttl)->getTimestamp() - time();
        }

        // We treat 0 as a valid value.
        if (is_int($ttl)) {
            return $ttl;
        }

        return null;
    }

View on GitHub (pinned to 6ab7b0404d)

Solutions

  1. Wrap single values in an array: $cache->getMultiple(['foo']) instead of $cache->getMultiple('foo').
  2. Decode JSON to arrays: json_decode($json, true).
  3. Guard at the boundary: if (!is_iterable($keys)) { throw new \InvalidArgumentException('keys must be iterable'); } with context about the origin.

Example fix

// before
$flags = $cache->getMultiple('isMobile');

// after
$flags = $cache->getMultiple(['isMobile', 'isTablet']);
Defensive patterns

Strategy: validation

Validate before calling

if (!is_iterable($keys)) {
    $keys = [$keys]; // or throw with context
}
$flags = $cache->getMultiple($keys);

Type guard

function isKeyList(mixed $keys): bool
{
    return is_iterable($keys)
        && (!is_array($keys) || array_all($keys, 'is_string'));
}

Try / catch

try {
    $cache->getMultiple($keys);
} catch (CacheInvalidArgumentException $e) {
    // 'Keys must be iterable.' — normalize input and retry
    $cache->getMultiple((array) $keys);
}

Prevention

When it happens

Trigger: $cache->getMultiple('foo') (single string instead of an array of keys), $cache->setMultiple(json_decode($json)) where the decode without true yields stdClass, $cache->deleteMultiple(null), or a refactor that changed a supplier method's return from array to scalar while callers still passed it to a *Multiple method.

Common situations: Confusing singular get/set/delete with their *Multiple counterparts; feeding decoded JSON objects directly; passing a single key 'for convenience'; generators already consumed (less common — those stay iterable but yield nothing).

Related errors


AI-assisted analysis of serbanghita/Mobile-Detect@6ab7b0404d (2026-08-21). Data as JSON: /api/errors/9d43f136b957274c. Report an issue: GitHub.