serbanghita/Mobile-Detect · error · CacheInvalidArgumentException

TTL must be null, int, or DateInterval.

Error message

TTL must be null, int, or DateInterval.

What it means

The TTL (time-to-live) argument passed to Cache::set() or Cache::setMultiple() is not one of the three types PSR-16 allows: null, int (seconds), or a DateInterval. checkTtl() at src/Cache/Cache.php:229 enforces this before storing. Like the key check, the parameter is intentionally untyped for cross-version PSR compatibility, so a bad type fails here at runtime rather than as a TypeError. (Note: an int TTL of 0 or less is valid and deletes the entry, per PSR-16 expiration semantics.)

Source

Thrown at src/Cache/Cache.php:229

        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;
    }

    /**
     * @param mixed $iterable
     * @return iterable<mixed>
     * @throws CacheInvalidArgumentException
     */
    protected function checkIterable($iterable, string $argName): iterable
    {
        if (!is_iterable($iterable)) {
            throw new CacheInvalidArgumentException(sprintf('%s must be iterable.', ucfirst($argName)));
        }

        return $iterable;
    }

View on GitHub (pinned to 6ab7b0404d)

Solutions

  1. Cast numeric config values at load time: $config['cacheTtl'] = (int) env('CACHE_TTL', 86400).
  2. Use DateInterval for relative durations: new \DateInterval('PT1H').
  3. Pass null explicitly to store the entry without expiry.

Example fix

// before
$detect = new MobileDetect(['cacheTtl' => '86400']); // string from env/config
$detect->isMobile(); // TTL must be null, int, or DateInterval.

// after
$detect = new MobileDetect(['cacheTtl' => 86400]);
// or: ['cacheTtl' => new \DateInterval('P1D')]
Defensive patterns

Strategy: type-guard

Validate before calling

$ttl = is_numeric($ttl) ? (int) $ttl : $ttl; // absorb string '86400' from env
$cache->set($key, $value, $ttl); // safe: null|int|DateInterval now

Type guard

/** @param mixed $ttl */
function isValidTtl(mixed $ttl): bool
{
    return $ttl === null || is_int($ttl) || $ttl instanceof \DateInterval;
}

Try / catch

try {
    $cache->set($key, $value, $configTtl);
} catch (CacheInvalidArgumentException $e) {
    // TTL arrived as a string from config; coerce and retry once
    $cache->set($key, $value, (int) $configTtl);
}

Prevention

When it happens

Trigger: $cache->set('k', $v, '3600') (numeric string), $cache->set('k', $v, 3600.5) (float), $cache->set('k', $v, new DateTime('+1 hour')) (DateTime, not DateInterval), or a '1 day' style string. Via MobileDetect, setting the 'cacheTtl' config (default int 86400 at src/MobileDetect.php:261) to a string triggers it on the first isMobile()/isTablet()/is() cache write.

Common situations: Loading cacheTtl from an .env file or JSON config where everything arrives as a string; reusing TTL values from libraries that accept text durations; confusing DateTime with DateInterval when converting from a stored timestamp or schedule.

Related errors


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