maxAttempts = $maxAttempts; $this->windowSeconds = $windowSeconds; $isNew = !is_file($storagePath); $this->db = new PDO('sqlite:' . $storagePath); $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); if ($isNew) { $this->db->exec( 'CREATE TABLE attempts ( ip TEXT NOT NULL, created_at INTEGER NOT NULL )' ); $this->db->exec('CREATE INDEX idx_attempts_ip ON attempts (ip, created_at)'); } } public function tooManyAttempts(string $ip): bool { $this->cleanup($ip); $stmt = $this->db->prepare('SELECT COUNT(*) FROM attempts WHERE ip = :ip AND created_at > :since'); $stmt->execute([ ':ip' => $ip, ':since' => time() - $this->windowSeconds, ]); return (int) $stmt->fetchColumn() >= $this->maxAttempts; } public function recordAttempt(string $ip): void { $stmt = $this->db->prepare('INSERT INTO attempts (ip, created_at) VALUES (:ip, :now)'); $stmt->execute([ ':ip' => $ip, ':now' => time(), ]); } private function cleanup(string $ip): void { $stmt = $this->db->prepare('DELETE FROM attempts WHERE ip = :ip AND created_at <= :since'); $stmt->execute([ ':ip' => $ip, ':since' => time() - $this->windowSeconds, ]); } }