addType('id', 'integer'); $this->addType('endpointKey', 'string'); $this->addType('remainingSecond', 'integer'); $this->addType('remainingMinute', 'integer'); $this->addType('remainingHour', 'integer'); $this->addType('remainingDay', 'integer'); $this->addType('limitSecond', 'integer'); $this->addType('limitMinute', 'integer'); $this->addType('limitHour', 'integer'); $this->addType('limitDay', 'integer'); $this->addType('resetSecond', 'integer'); $this->addType('resetMinuteAt', 'integer'); $this->addType('resetHour', 'integer'); $this->addType('updatedAt', 'integer'); } /** * Check if rate limit allows a request at current time */ public function canProcess(): bool { $effective = $this->getEffectiveRemaining(); foreach ($effective as $remaining) { if ($remaining !== null && $remaining <= 0) { return false; } } return true; } /** * Get effective remaining count considering window resets * * @return array{ * second: ?int, * minute: ?int, * hour: ?int, * day: ?int * } */ public function getEffectiveRemaining(): array { return [ 'second' => $this->getEffectiveWindowRemaining($this->limitSecond, $this->remainingSecond, $this->resetSecond), 'minute' => $this->getEffectiveWindowRemaining($this->limitMinute, $this->remainingMinute, $this->resetMinuteAt), 'hour' => $this->getEffectiveWindowRemaining($this->limitHour, $this->remainingHour, $this->resetHour), 'day' => $this->getEffectiveWindowRemaining($this->limitDay, $this->remainingDay, null), ]; } /** * Calculate seconds until next available slot */ public function getSecondsUntilAvailable(): int { $now = time(); $effective = $this->getEffectiveRemaining(); $waits = []; if ($effective['second'] !== null && $effective['second'] <= 0) { $waits[] = $this->getWaitTime($this->resetSecond, 1, $now); } if ($effective['minute'] !== null && $effective['minute'] <= 0) { $waits[] = $this->getWaitTime($this->resetMinuteAt, 60, $now); } if ($effective['hour'] !== null && $effective['hour'] <= 0) { $waits[] = $this->getWaitTime($this->resetHour, 3600, $now); } if ($effective['day'] !== null && $effective['day'] <= 0) { $waits[] = 3600; } if ($waits === []) { return 0; } return max($waits); } public function jsonSerialize(): array { $effective = $this->getEffectiveRemaining(); return [ 'id' => $this->id, 'endpoint_key' => $this->endpointKey, 'remaining_second' => $effective['second'], 'remaining_minute' => $effective['minute'], 'remaining_hour' => $effective['hour'], 'remaining_day' => $effective['day'], 'limit_second' => $this->limitSecond !== null && $this->limitSecond > 0 ? $this->limitSecond : null, 'limit_minute' => $this->limitMinute !== null && $this->limitMinute > 0 ? $this->limitMinute : null, 'limit_hour' => $this->limitHour !== null && $this->limitHour > 0 ? $this->limitHour : null, 'limit_day' => $this->limitDay !== null && $this->limitDay > 0 ? $this->limitDay : null, 'reset_second' => $this->limitSecond !== null && $this->limitSecond > 0 ? $this->resetSecond : null, 'reset_minute_at' => $this->limitMinute !== null && $this->limitMinute > 0 ? $this->resetMinuteAt : null, 'reset_hour' => $this->limitHour !== null && $this->limitHour > 0 ? $this->resetHour : null, 'can_process' => $this->canProcess(), 'seconds_until_available' => $this->getSecondsUntilAvailable(), 'updated_at' => $this->updatedAt, ]; } private function getEffectiveWindowRemaining(?int $limit, ?int $remaining, ?int $resetAt): ?int { if ($limit === null || $limit <= 0) { return null; } if ($resetAt !== null && $resetAt > 0 && time() >= $resetAt) { return $limit; } return $remaining ?? $limit; } private function getWaitTime(?int $resetAt, int $defaultWait, int $now): int { if ($resetAt === null || $resetAt <= 0) { return $defaultWait; } return max(0, $resetAt - $now); } }