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

  1. Cast or coerce the key before calling: $cache->get((string) $key).
  2. Trace the value's origin (gettype($key)) and fix the data source so keys are always strings.
  3. 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

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


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