{"record":{"id":"a821bab8350edcb7","repo":"doctrine/cache","slug":"expected-expiration-to-be-an-instance-of-datetimeinterface-a821ba","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/TypedCacheItem.php","lineNumber":62,"sourceCode":"\n    public function set(mixed $value): static\n    {\n        $this->value = $value;\n\n        return $this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function expiresAt($expiration): static\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                get_debug_type($expiration)\n            ));\n        }\n\n        return $this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function expiresAfter($time): static\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":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/doctrine/cache/blob/e0a9919443c1e918251c23b65eddd9a66fafacb3/lib/Doctrine/Common/Cache/Psr6/TypedCacheItem.php#L44-L80","documentation":"TypedCacheItem::expiresAt() accepts only DateTimeInterface or null and uses get_debug_type() to report the offending type in a TypeError. It is the strict-typed variant of CacheItem used by the typed cache facade.","triggerScenarios":"Passing an int timestamp, date string, DateInterval, or non-DateTimeInterface object to expiresAt() on a TypedCacheItem (or via the typed cache wrapper).","commonSituations":"Same confusion as CacheItem: Unix timestamp instead of DateTime; unparsed date string from JSON/API; switching from the untyped to typed item class and old code no longer type-checks (though native types usually catch this earlier).","solutions":["Wrap timestamps: new DateTimeImmutable('@' . $ts); parse strings: new DateTimeImmutable($str)","Use expiresAfter() for relative TTLs","Pass null to remove expiration"],"exampleFix":"// before\n$item->expiresAt($json['expires']); // string '2030-01-01T00:00:00Z'\n// after\n$item->expiresAt(new DateTimeImmutable($json['expires']));","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) { /* handle invalid expiration */ }","preventionTips":["Normalize all expiry inputs to DateTimeImmutable early","Do not pass Unix timestamps to expiresAt; use new DateTimeImmutable('@'.$ts)","Prefer expiresAfter for relative expiries"],"tags":["php","psr6","cache","typeerror","typed"],"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"}