{"record":{"id":"c56997ccc5d135bf","repo":"serbanghita/Mobile-Detect","slug":"invalid-key-key-must-be-alphanumeric-can-con","errorCode":null,"errorMessage":"Invalid key: '$key'. Must be alphanumeric, can contain _ and . and can be maximum of 64 chars.","messagePattern":"Invalid key: '\\$key'\\. Must be alphanumeric, can contain _ and \\. and can be maximum of 64 chars\\.","errorType":"exception","errorClass":"CacheInvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Cache/Cache.php","lineNumber":216,"sourceCode":"        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        }\n\n        return $ttl;\n    }\n","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/serbanghita/Mobile-Detect/blob/6ab7b0404df1da8da2aa2c56634362842d9c2a23/src/Cache/Cache.php#L198-L234","documentation":"A cache key was rejected by the strict policy in checkKey() (src/Cache/Cache.php:216): it must be non-empty, at most 64 characters, and match /^[A-Za-z0-9_.]{1,64}$/ — letters, digits, underscore and dot only. Hyphens, colons, spaces, slashes and UTF-8 characters are all forbidden, as is the empty string. This is an aggressive reading of the PSR-16 'valid characters' rule: invalid keys throw instead of being silently transformed.","triggerScenarios":"$cache->get('user-agent:iphone') (colon), $cache->set('is-mobile!', true) (exclamation), $cache->get('ua/iphone') (slash), $cache->get('') (empty), a key over 64 chars, or a custom 'cacheKeyFn' config that returns raw keys. Internally, createCacheKey() builds 'rule:userAgent:flatHeaders' and relies on the default sha1 callback to turn it into a 40-char hex string — replace that callback with a passthrough and this check fails on every detection call.","commonSituations":"Changing MobileDetect's 'cacheKeyFn' from 'sha1' to something returning raw or base64-encoded keys (base64_encode output contains +, /, = which are invalid); porting cache keys from another PSR-16 implementation that allowed the wider reserved set (A-Z a-z 0-9 _ . : / - ( ) ); copy-pasting URL fragments or UA substrings as keys.","solutions":["Hash or sanitize keys before use: $key = sha1($rawKey);, or preg_replace('/[^A-Za-z0-9_.]/', '_', $rawKey) then truncate to 64 chars.","If you customize MobileDetect's cacheKeyFn, keep a hashing callback ('sha1', 'md5', or a hash() closure) — never a passthrough — because the internal composite key contains colons and raw User-Agent text.","Reject or truncate keys over 64 characters at your own boundary so the cache never sees them."],"exampleFix":"// before\n$detect = new MobileDetect(['cacheKeyFn' => fn($k) => $k]);\n$detect->isMobile(); // Invalid key: 'mobile:Mozilla/5.0 ...' (colons, >64 chars)\n\n// after\n$detect = new MobileDetect(); // default 'cacheKeyFn' => 'sha1'","handlingStrategy":"validation","validationCode":"$safeKey = preg_replace('/[^A-Za-z0-9_.]/', '_', $rawKey);\nif (strlen($safeKey) > 64) {\n    $safeKey = substr($safeKey, 0, 64);\n}\n$cache->get($safeKey);","typeGuard":"function isValidCacheKey(string $key): bool\n{\n    return preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key) === 1;\n}","tryCatchPattern":"try {\n    $cache->get($key);\n} catch (CacheInvalidArgumentException $e) {\n    if (str_contains($e->getMessage(), 'Invalid key')) {\n        $value = $cache->get(sha1($key)); // retry with a hashed, always-valid key\n    }\n}","preventionTips":["Hash composite keys (sha1/md5) instead of embedding raw UA text or colons.","If you change MobileDetect's cacheKeyFn, verify its output against the same [A-Za-z0-9_.]{1,64} rule.","Add a unit test asserting your key generator passes the regex for your nastiest real User-Agent."],"tags":["cache","psr-16","key-format","validation","php"],"backgroundTag":"invalid-cache-key","analyzedSha":"6ab7b0404df1da8da2aa2c56634362842d9c2a23","analyzedAt":"2026-08-21T04:49:48.090Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}