serbanghita/Mobile-Detect · error · CacheInvalidArgumentException
Cache key must be a string.
Error message
Cache key must be a string.
What it means
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.
Source
Thrown at src/Cache/Cache.php:212
public function deleteMultiple($keys): bool
{
$keys = $this->checkIterable($keys, 'keys');
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.');
}View on GitHub (pinned to 6ab7b0404d)
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.
Example fix
// before $id = 42; $cache->set($id, $isMobile); // CacheInvalidArgumentException: Cache key must be a string. // after $cache->set((string) $id, $isMobile);
Defensive patterns
Strategy: type-guard
Validate before calling
if (!is_string($key)) {
throw new \InvalidArgumentException(sprintf('Cache key must be string, %s given', get_debug_type($key)));
}
$value = $cache->get($key); Type guard
function isCacheKey(mixed $key): bool
{
return is_string($key) && $key !== '' && preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key) === 1;
} Try / catch
use Detection\Cache\CacheInvalidArgumentException;
try {
$cache->set($key, $value);
} catch (CacheInvalidArgumentException $e) {
// key rejected before storage; log and rebuild the key
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- TTL must be null, int, or DateInterval.
- %s must be iterable.
- Invalid key: '$key'. Must be alphanumeric, can contain _ and
- IS_MOBILE_ERR
- IS_TABLET_ERR
AI-assisted analysis of serbanghita/Mobile-Detect@6ab7b0404d (2026-08-21).
Data as JSON: /api/errors/02cbd9ca92864844.
Report an issue: GitHub.