{"record":{"id":"eab1ab5a1cec3019","repo":"doctrine/cache","slug":"expected-expiration-to-be-an-instance-of-datetimeinterface","errorCode":null,"errorMessage":"Expected $expiration to be an instance of DateTimeInterface or null, got %s","messagePattern":"Expected \\$expiration to be an instance of DateTimeInterface or null, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/Doctrine/Common/Cache/Psr6/CacheItem.php","lineNumber":81,"sourceCode":"     */\n    public function set($value): self\n    {\n        $this->value = $value;\n\n        return $this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function expiresAt($expiration): self\n    {\n        if ($expiration === null) {\n            $this->expiry = null;\n        } elseif ($expiration instanceof DateTimeInterface) {\n            $this->expiry = (float) $expiration->format('U.u');\n        } else {\n            throw new TypeError(sprintf(\n                'Expected $expiration to be an instance of DateTimeInterface or null, got %s',\n                is_object($expiration) ? get_class($expiration) : gettype($expiration)\n            ));\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)) {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/doctrine/cache/blob/e0a9919443c1e918251c23b65eddd9a66fafacb3/lib/Doctrine/Common/Cache/Psr6/CacheItem.php#L63-L99","documentation":"CacheItem::expiresAt() accepts only a DateTimeInterface implementation (DateTime/DateTimeImmutable) or null; anything else triggers a TypeError. This enforces the PSR-6 contract that expiration must be an absolute point in time.","triggerScenarios":"Calling $item->expiresAt($value) where $value is an int timestamp, a date string, a DateInterval, a custom date-like object not implementing DateTimeInterface, or a string from an API.","commonSituations":"Passing a Unix timestamp integer (common confusion with expiresAfter); passing a date string from JSON/config without parsing; using Carbon versions/objects that do not implement DateTimeInterface (rare, Carbon does implement it).","solutions":["Convert to a DateTime object first, e.g. new DateTimeImmutable('@' . $ts) for timestamps or new DateTimeImmutable($dateString) for strings","Use expiresAfter() instead if you have a relative TTL (int seconds or DateInterval)","Pass null explicitly to clear the expiration rather than 0 or false"],"exampleFix":"// before\n$item->expiresAt(time() + 3600); // int\n// after\n$item->expiresAt(new DateTimeImmutable('+1 hour'));","handlingStrategy":"type-guard","validationCode":"if ($expiration !== null && !$expiration instanceof \\DateTimeInterface) { throw new InvalidArgumentException('expiration must be DateTimeInterface or null'); }","typeGuard":"function acceptsExpiration(mixed $v): bool { return $v === null || $v instanceof \\DateTimeInterface; }","tryCatchPattern":"try { $item->expiresAt($expiration); } catch (\\TypeError $e) { /* convert and retry */ }","preventionTips":["Convert timestamps/strings to DateTimeImmutable before calling expiresAt","Use expiresAfter for relative TTLs","Remember DateInterval is not accepted by expiresAt"],"tags":["php","psr6","cache","typeerror","expiration"],"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"}