{"record":{"id":"9d43f136b957274c","repo":"serbanghita/Mobile-Detect","slug":"s-must-be-iterable","errorCode":null,"errorMessage":"%s must be iterable.","messagePattern":"(.+?) must be iterable\\.","errorType":"exception","errorClass":"CacheInvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Cache/Cache.php","lineNumber":243,"sourceCode":"     */\n    protected function checkTtl($ttl): int|DateInterval|null\n    {\n        if ($ttl !== null && !is_int($ttl) && !($ttl instanceof DateInterval)) {\n            throw new CacheInvalidArgumentException('TTL must be null, int, or DateInterval.');\n        }\n\n        return $ttl;\n    }\n\n    /**\n     * @param mixed $iterable\n     * @return iterable<mixed>\n     * @throws CacheInvalidArgumentException\n     */\n    protected function checkIterable($iterable, string $argName): iterable\n    {\n        if (!is_iterable($iterable)) {\n            throw new CacheInvalidArgumentException(sprintf('%s must be iterable.', ucfirst($argName)));\n        }\n\n        return $iterable;\n    }\n\n    protected function getTTL(DateInterval|int|null $ttl): ?int\n    {\n        if ($ttl instanceof DateInterval) {\n            return (new DateTime())->add($ttl)->getTimestamp() - time();\n        }\n\n        // We treat 0 as a valid value.\n        if (is_int($ttl)) {\n            return $ttl;\n        }\n\n        return null;\n    }","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/serbanghita/Mobile-Detect/blob/6ab7b0404df1da8da2aa2c56634362842d9c2a23/src/Cache/Cache.php#L225-L261","documentation":"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.","triggerScenarios":"$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.","commonSituations":"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).","solutions":["Wrap single values in an array: $cache->getMultiple(['foo']) instead of $cache->getMultiple('foo').","Decode JSON to arrays: json_decode($json, true).","Guard at the boundary: if (!is_iterable($keys)) { throw new \\InvalidArgumentException('keys must be iterable'); } with context about the origin."],"exampleFix":"// before\n$flags = $cache->getMultiple('isMobile');\n\n// after\n$flags = $cache->getMultiple(['isMobile', 'isTablet']);","handlingStrategy":"validation","validationCode":"if (!is_iterable($keys)) {\n    $keys = [$keys]; // or throw with context\n}\n$flags = $cache->getMultiple($keys);","typeGuard":"function isKeyList(mixed $keys): bool\n{\n    return is_iterable($keys)\n        && (!is_array($keys) || array_all($keys, 'is_string'));\n}","tryCatchPattern":"try {\n    $cache->getMultiple($keys);\n} catch (CacheInvalidArgumentException $e) {\n    // 'Keys must be iterable.' — normalize input and retry\n    $cache->getMultiple((array) $keys);\n}","preventionTips":["Call *Multiple methods only with array literals: getMultiple(['a', 'b']).","json_decode($json, true) so decoded structures are arrays, not stdClass.","Re-check call sites when refactoring a method that feeds cache keys to return arrays."],"tags":["cache","psr-16","iterable","invalid-argument","php"],"backgroundTag":"invalid-cache-argument","analyzedSha":"6ab7b0404df1da8da2aa2c56634362842d9c2a23","analyzedAt":"2026-08-21T04:49:48.090Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}