{"record":{"id":"2211716f7c4add74","repo":"serbanghita/Mobile-Detect","slug":"ttl-must-be-null-int-or-dateinterval","errorCode":null,"errorMessage":"TTL must be null, int, or DateInterval.","messagePattern":"TTL must be null, int, or DateInterval\\.","errorType":"exception","errorClass":"CacheInvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Cache/Cache.php","lineNumber":229,"sourceCode":"        if (!is_string($key)) {\n            throw new CacheInvalidArgumentException('Cache key must be a string.');\n        }\n\n        if ($key === '' || !preg_match('/^[A-Za-z0-9_.]{1,64}$/', $key)) {\n            throw new CacheInvalidArgumentException(\"Invalid key: '$key'. Must be alphanumeric, can contain _ and . and can be maximum of 64 chars.\");\n        }\n\n        return $key;\n    }\n\n    /**\n     * @param mixed $ttl\n     * @throws CacheInvalidArgumentException\n     */\n    protected function checkTtl($ttl): int|DateInterval|null\n    {\n        if ($ttl !== null && !is_int($ttl) && !($ttl instanceof DateInterval)) {\n            throw new CacheInvalidArgumentException('TTL must be null, int, or DateInterval.');\n        }\n\n        return $ttl;\n    }\n\n    /**\n     * @param mixed $iterable\n     * @return iterable<mixed>\n     * @throws CacheInvalidArgumentException\n     */\n    protected function checkIterable($iterable, string $argName): iterable\n    {\n        if (!is_iterable($iterable)) {\n            throw new CacheInvalidArgumentException(sprintf('%s must be iterable.', ucfirst($argName)));\n        }\n\n        return $iterable;\n    }","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/serbanghita/Mobile-Detect/blob/6ab7b0404df1da8da2aa2c56634362842d9c2a23/src/Cache/Cache.php#L211-L247","documentation":"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.)","triggerScenarios":"$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.","commonSituations":"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.","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."],"exampleFix":"// before\n$detect = new MobileDetect(['cacheTtl' => '86400']); // string from env/config\n$detect->isMobile(); // TTL must be null, int, or DateInterval.\n\n// after\n$detect = new MobileDetect(['cacheTtl' => 86400]);\n// or: ['cacheTtl' => new \\DateInterval('P1D')]","handlingStrategy":"type-guard","validationCode":"$ttl = is_numeric($ttl) ? (int) $ttl : $ttl; // absorb string '86400' from env\n$cache->set($key, $value, $ttl); // safe: null|int|DateInterval now","typeGuard":"/** @param mixed $ttl */\nfunction isValidTtl(mixed $ttl): bool\n{\n    return $ttl === null || is_int($ttl) || $ttl instanceof \\DateInterval;\n}","tryCatchPattern":"try {\n    $cache->set($key, $value, $configTtl);\n} catch (CacheInvalidArgumentException $e) {\n    // TTL arrived as a string from config; coerce and retry once\n    $cache->set($key, $value, (int) $configTtl);\n}","preventionTips":["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."],"tags":["cache","psr-16","ttl","invalid-argument","php"],"backgroundTag":"invalid-cache-ttl","analyzedSha":"6ab7b0404df1da8da2aa2c56634362842d9c2a23","analyzedAt":"2026-08-21T04:49:48.090Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}