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
- Cast numeric config values at load time: $config['cacheTtl'] = (int) env('CACHE_TTL', 86400).
- Use DateInterval for relative durations: new \DateInterval('PT1H').
- 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
- Cast TTLs when loading config: 'cacheTtl' => (int) env('DETECT_CACHE_TTL', 86400).
- Use DateInterval for relative durations; never DateTime or '1 day' strings.
- Validate config once at boot, not on every cache call.
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
- Cache key must be a string.
- %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/2211716f7c4add74.
Report an issue: GitHub.