serbanghita/Mobile-Detect · error · CacheInvalidArgumentException

Invalid key: '$key'. Must be alphanumeric, can contain _ and

Error message

Invalid key: '$key'. Must be alphanumeric, can contain _ and . and can be maximum of 64 chars.

What it means

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.

Source

Thrown at src/Cache/Cache.php:216

        foreach ($keys as $key) {
            $this->delete($key);
        }

        return true;
    }

    /**
     * @param mixed $key
     * @throws CacheInvalidArgumentException
     */
    protected function checkKey($key): string
    {
        if (!is_string($key)) {
            throw new CacheInvalidArgumentException('Cache key must be a string.');
        }

        if ($key === '' || !preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key)) {
            throw new CacheInvalidArgumentException("Invalid key: '$key'. Must be alphanumeric, can contain _ and . and can be maximum of 64 chars.");
        }

        return $key;
    }

    /**
     * @param mixed $ttl
     * @throws CacheInvalidArgumentException
     */
    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;
    }

View on GitHub (pinned to 6ab7b0404d)

Solutions

  1. Hash or sanitize keys before use: $key = sha1($rawKey);, or preg_replace('/[^A-Za-z0-9_.]/', '_', $rawKey) then truncate to 64 chars.
  2. 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.
  3. Reject or truncate keys over 64 characters at your own boundary so the cache never sees them.

Example fix

// before
$detect = new MobileDetect(['cacheKeyFn' => fn($k) => $k]);
$detect->isMobile(); // Invalid key: 'mobile:Mozilla/5.0 ...' (colons, >64 chars)

// after
$detect = new MobileDetect(); // default 'cacheKeyFn' => 'sha1'
Defensive patterns

Strategy: validation

Validate before calling

$safeKey = preg_replace('/[^A-Za-z0-9_.]/', '_', $rawKey);
if (strlen($safeKey) > 64) {
    $safeKey = substr($safeKey, 0, 64);
}
$cache->get($safeKey);

Type guard

function isValidCacheKey(string $key): bool
{
    return preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key) === 1;
}

Try / catch

try {
    $cache->get($key);
} catch (CacheInvalidArgumentException $e) {
    if (str_contains($e->getMessage(), 'Invalid key')) {
        $value = $cache->get(sha1($key)); // retry with a hashed, always-valid key
    }
}

Prevention

When it happens

Trigger: $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.

Common situations: 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.

Related errors


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