toString(), userId: $userId, email: $email, tenantId: $tenantId, role: $role, schoolName: $schoolName, createdAt: $createdAt, expiresAt: $createdAt->modify(sprintf('+%d days', self::EXPIRATION_DAYS)), studentId: $studentId, relationshipType: $relationshipType, ); $token->recordEvent(new ActivationTokenGenerated( tokenId: $token->id, userId: $userId, email: $email, tenantId: $tenantId, occurredOn: $createdAt, )); return $token; } /** * Reconstitute an ActivationToken from storage. * Does NOT record domain events (this is not a new creation). * * @internal For use by Infrastructure layer only */ public static function reconstitute( ActivationTokenId $id, string $tokenValue, string $userId, string $email, TenantId $tenantId, string $role, string $schoolName, DateTimeImmutable $createdAt, DateTimeImmutable $expiresAt, ?DateTimeImmutable $usedAt, ?string $studentId = null, ?string $relationshipType = null, ): self { $token = new self( id: $id, tokenValue: $tokenValue, userId: $userId, email: $email, tenantId: $tenantId, role: $role, schoolName: $schoolName, createdAt: $createdAt, expiresAt: $expiresAt, studentId: $studentId, relationshipType: $relationshipType, ); $token->usedAt = $usedAt; return $token; } public function isExpired(DateTimeImmutable $at): bool { return $at >= $this->expiresAt; } public function isUsed(): bool { return $this->usedAt !== null; } /** * Validate that the token can be used (not expired, not already used). * Does NOT mark the token as used - use use() for that after successful activation. * * @throws ActivationTokenAlreadyUsedException if token was already used * @throws ActivationTokenExpiredException if token is expired */ public function validateForUse(DateTimeImmutable $at): void { if ($this->isUsed()) { throw ActivationTokenAlreadyUsedException::forToken($this->id); } if ($this->isExpired($at)) { throw ActivationTokenExpiredException::forToken($this->id); } } /** * Mark the token as used. Should only be called after successful user activation. * * @throws ActivationTokenAlreadyUsedException if token was already used * @throws ActivationTokenExpiredException if token is expired */ public function use(DateTimeImmutable $at): void { $this->validateForUse($at); $this->usedAt = $at; $this->recordEvent(new ActivationTokenUsed( tokenId: $this->id, userId: $this->userId, occurredOn: $at, )); } }