{"record":{"id":"a6a5fb7780128ac4","repo":"doctrine/cache","slug":"expected-time-to-be-either-an-integer-an-instance-of","errorCode":null,"errorMessage":"Expected $time to be either an integer, an instance of DateInterval or null, got %s","messagePattern":"Expected \\$time to be either an integer, an instance of DateInterval or null, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/Doctrine/Common/Cache/Psr6/CacheItem.php","lineNumber":102,"sourceCode":"            ));\n        }\n\n        return $this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function expiresAfter($time): self\n    {\n        if ($time === null) {\n            $this->expiry = null;\n        } elseif ($time instanceof DateInterval) {\n            $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u');\n        } elseif (is_int($time)) {\n            $this->expiry = $time + microtime(true);\n        } else {\n            throw new TypeError(sprintf(\n                'Expected $time to be either an integer, an instance of DateInterval or null, got %s',\n                is_object($time) ? get_class($time) : gettype($time)\n            ));\n        }\n\n        return $this;\n    }\n\n    /**\n     * @internal\n     */\n    public function getExpiry(): ?float\n    {\n        return $this->expiry;\n    }\n}\n","sourceCodeStart":84,"sourceCodeEnd":119,"githubUrl":"https://github.com/doctrine/cache/blob/e0a9919443c1e918251c23b65eddd9a66fafacb3/lib/Doctrine/Common/Cache/Psr6/CacheItem.php#L84-L119","documentation":"This TypeError is thrown by CacheItem::expiresAfter() when the caller passes an expiry value that is not one of the three shapes PSR-6/PSR-16 accept for a relative TTL: an integer number of seconds, a DateInterval, or null. Because the null, DateInterval, and is_int branches are handled before it, this guard only fires on invalid argument types — e.g. a numeric string like '3600', a DateTimeImmutable, a float wrapped in a string, or an array — typically meaning the caller intended a relative expiration but supplied the wrong PHP type instead of casting to int or using DateInterval.","triggerScenarios":"Calling $item->expiresAfter($value) with a float, string like '3600' or 'PT1H', a DateTime/DateTimeImmutable, or a numeric value from config.","commonSituations":"Reading a TTL string from environment/config ('3600') without casting; passing a DateTime object (mixing up with expiresAt); passing a float from microtime math.","solutions":["Cast numeric TTLs to int: expiresAfter((int) $ttl)","Parse interval strings into DateInterval: new DateInterval('PT1H')","Use expiresAt() with a DateTimeImmutable if you have an absolute deadline"],"exampleFix":"// before\n$item->expiresAfter($config['ttl']); // string '3600'\n// after\n$item->expiresAfter((int) $config['ttl']);","handlingStrategy":"type-guard","validationCode":"if ($time !== null && !is_int($time) && !$time instanceof \\DateInterval) { throw new InvalidArgumentException('time must be int, DateInterval or null'); }","typeGuard":"function acceptsTtl(mixed $v): bool { return $v === null || is_int($v) || $v instanceof \\DateInterval; }","tryCatchPattern":"try { $item->expiresAfter($ttl); } catch (\\TypeError $e) { $item->expiresAfter((int) $ttl); }","preventionTips":["Cast config/env TTL strings to int at load time","Use DateInterval for 'PT1H'-style durations","Use expiresAt with DateTime for absolute deadlines"],"tags":["php","psr6","cache","typeerror","ttl"],"backgroundTag":"type-mismatch","analyzedSha":"e0a9919443c1e918251c23b65eddd9a66fafacb3","analyzedAt":"2026-09-13T19:23:46.714Z","contentChangedAt":"2026-09-13T19:23:46.714Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}