{"record":{"id":"02cbd9ca92864844","repo":"serbanghita/Mobile-Detect","slug":"cache-key-must-be-a-string","errorCode":null,"errorMessage":"Cache key must be a string.","messagePattern":"Cache key must be a string\\.","errorType":"exception","errorClass":"CacheInvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Cache/Cache.php","lineNumber":212,"sourceCode":"    public function deleteMultiple($keys): bool\n    {\n        $keys = $this->checkIterable($keys, 'keys');\n\n        foreach ($keys as $key) {\n            $this->delete($key);\n        }\n\n        return true;\n    }\n\n    /**\n     * @param mixed $key\n     * @throws CacheInvalidArgumentException\n     */\n    protected function checkKey($key): string\n    {\n        if (!is_string($key)) {\n            throw new CacheInvalidArgumentException('Cache key must be a string.');\n        }\n\n        if ($key === '' || !preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key)) {\n            throw new CacheInvalidArgumentException(\"Invalid key: '$key'. Must be alphanumeric, can contain _ and . and can be maximum of 64 chars.\");\n        }\n\n        return $key;\n    }\n\n    /**\n     * @param mixed $ttl\n     * @throws CacheInvalidArgumentException\n     */\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        }","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/serbanghita/Mobile-Detect/blob/6ab7b0404df1da8da2aa2c56634362842d9c2a23/src/Cache/Cache.php#L194-L230","documentation":"The bundled PSR-16 cache (Detection\\Cache\\Cache) was called with a cache key that is not a PHP string (e.g. int, null, array). Because the public PSR-16 methods are deliberately left without scalar parameter types to stay Liskov-compatible with PSR-16 v1/v2/v3 hosts (see the class docblock referencing issue #989), an invalid type is not rejected at call time and instead surfaces as this CacheInvalidArgumentException from checkKey() (src/Cache/Cache.php:212). Every cache operation (get, set, delete, has) runs this check before touching storage.","triggerScenarios":"Calling $cache->get(123), $cache->set(null, $value), $cache->has($someArray), or passing a variable sourced from array_keys()/decoded JSON without casting, e.g. $cache->get($data['key']) where the value is an int. Note MobileDetect itself always passes the sha1-hashed string built by createCacheKey(), so this error comes from direct use of the Cache class, not from detection calls.","commonSituations":"Keys sourced from array indexes (PHP casts numeric-string keys to int), decoded JSON with numeric keys, passing null by accident when a lookup misses ('user_' . ($id ?? null)), or integrations that assumed the PSR-16 methods were typed and would fail fast with a TypeError.","solutions":["Cast or coerce the key before calling: $cache->get((string) $key).","Trace the value's origin (gettype($key)) and fix the data source so keys are always strings.","Add an is_string() guard at your own call boundary that throws a domain exception with better context than the library can provide."],"exampleFix":"// before\n$id = 42;\n$cache->set($id, $isMobile);  // CacheInvalidArgumentException: Cache key must be a string.\n\n// after\n$cache->set((string) $id, $isMobile);","handlingStrategy":"type-guard","validationCode":"if (!is_string($key)) {\n    throw new \\InvalidArgumentException(sprintf('Cache key must be string, %s given', get_debug_type($key)));\n}\n$value = $cache->get($key);","typeGuard":"function isCacheKey(mixed $key): bool\n{\n    return is_string($key) && $key !== '' && preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key) === 1;\n}","tryCatchPattern":"use Detection\\Cache\\CacheInvalidArgumentException;\n\ntry {\n    $cache->set($key, $value);\n} catch (CacheInvalidArgumentException $e) {\n    // key rejected before storage; log and rebuild the key\n}","preventionTips":["Cast numeric IDs to string at the boundary: $cache->get((string) $userId).","Route all keys through one key-builder helper so they are strings by construction.","Enable strict_types=1 in your own files so callers can't pass ints/floats where you declare string."],"tags":["cache","psr-16","invalid-argument","type-error","php"],"backgroundTag":"invalid-cache-key","analyzedSha":"6ab7b0404df1da8da2aa2c56634362842d9c2a23","analyzedAt":"2026-08-21T04:49:48.090Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}