chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit;
readonly class RateLimitResult
{
public function __construct(
private bool $allowed,
private int $limit,
private int $current,
private ?int $retryAfter = null
) {}
public static function allowed(int $limit, int $current): self
{
return new self(
allowed: true,
limit: $limit,
current: $current
);
}
public static function exceeded(int $limit, int $current, int $retryAfter): self
{
return new self(
allowed: false,
limit: $limit,
current: $current,
retryAfter: $retryAfter
);
}
public function isAllowed(): bool
{
return $this->allowed;
}
public function isExceeded(): bool
{
return !$this->allowed;
}
public function getLimit(): int
{
return $this->limit;
}
public function getCurrent(): int
{
return $this->current;
}
public function getRetryAfter(): ?int
{
return $this->retryAfter;
}
public function getRemainingRequests(): int
{
return max(0, $this->limit - $this->current);
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit;
use App\Framework\RateLimit\Storage\StorageInterface;
use App\Framework\RateLimit\TimeProvider\TimeProviderInterface;
use App\Framework\RateLimit\TimeProvider\SystemTimeProvider;
final readonly class RateLimiter
{
public function __construct(
private StorageInterface $storage,
private TimeProviderInterface $timeProvider = new SystemTimeProvider()
) {}
public function checkLimit(string $key, int $limit, int $window): RateLimitResult
{
$now = $this->timeProvider->getCurrentTime();
$windowStart = $now - $window;
// Sliding Window Log Implementierung
$requests = $this->storage->getRequestsInWindow($key, $windowStart, $now);
if (count($requests) >= $limit) {
$oldestRequest = min($requests);
$retryAfter = $oldestRequest + $window - $now;
return RateLimitResult::exceeded($limit, count($requests), max(1, $retryAfter));
}
// Request hinzufügen
$this->storage->addRequest($key, $now, $window);
return RateLimitResult::allowed($limit, count($requests) + 1);
}
public function checkTokenBucket(string $key, int $capacity, int $refillRate, int $tokens = 1): RateLimitResult
{
$now = $this->timeProvider->getCurrentTime();
$bucket = $this->storage->getTokenBucket($key);
if ($bucket === null) {
$bucket = new TokenBucket($capacity, $capacity, $now);
}
// Token nachfüllen
$timePassed = $now - $bucket->lastRefill;
$tokensToAdd = min($capacity - $bucket->tokens, intval($timePassed * $refillRate));
$bucket = new TokenBucket(
$bucket->capacity,
$bucket->tokens + $tokensToAdd,
$now
);
if ($bucket->tokens >= $tokens) {
$bucket = new TokenBucket(
$bucket->capacity,
$bucket->tokens - $tokens,
$bucket->lastRefill
);
$this->storage->saveTokenBucket($key, $bucket);
return RateLimitResult::allowed($capacity, $bucket->tokens);
}
$this->storage->saveTokenBucket($key, $bucket);
$retryAfter = intval(($tokens - $bucket->tokens) / $refillRate);
return RateLimitResult::exceeded($capacity, $bucket->tokens, $retryAfter);
}
public function reset(string $key): void
{
$this->storage->clear($key);
}
public function getUsage(string $key, int $window): int
{
$now = $this->timeProvider->getCurrentTime();
$windowStart = $now - $window;
return count($this->storage->getRequestsInWindow($key, $windowStart, $now));
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit\Storage;
use App\Framework\Cache\Cache;
use App\Framework\RateLimit\TokenBucket;
final readonly class CacheStorage implements StorageInterface
{
public function __construct(
private Cache $cache
) {}
public function getRequestsInWindow(string $key, int $windowStart, int $windowEnd): array
{
$requests = $this->cache->get("rate_limit:$key:requests") ?? [];
// Alte Requests entfernen
$requests = array_filter($requests, fn($timestamp) => $timestamp >= $windowStart);
// Cache aktualisieren
$this->cache->set("rate_limit:$key:requests", $requests, 3600);
return $requests;
}
public function addRequest(string $key, int $timestamp, int $ttl): void
{
$requests = $this->cache->get("rate_limit:$key:requests") ?? [];
$requests[] = $timestamp;
$this->cache->set("rate_limit:$key:requests", $requests, $ttl);
}
public function getTokenBucket(string $key): ?TokenBucket
{
$data = $this->cache->get("rate_limit:$key:bucket");
if ($data === null) {
return null;
}
return new TokenBucket(
capacity: $data['capacity'],
tokens: $data['tokens'],
lastRefill: $data['last_refill']
);
}
public function saveTokenBucket(string $key, TokenBucket $bucket): void
{
$data = [
'capacity' => $bucket->capacity,
'tokens' => $bucket->tokens,
'last_refill' => $bucket->lastRefill
];
$this->cache->set("rate_limit:$key:bucket", $data, 3600);
}
public function clear(string $key): void
{
$this->cache->forget("rate_limit:$key:requests");
$this->cache->forget("rate_limit:$key:bucket");
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit\Storage;
use App\Framework\RateLimit\TokenBucket;
interface StorageInterface
{
public function getRequestsInWindow(string $key, int $windowStart, int $windowEnd): array;
public function addRequest(string $key, int $timestamp, int $ttl): void;
public function getTokenBucket(string $key): ?TokenBucket;
public function saveTokenBucket(string $key, TokenBucket $bucket): void;
public function clear(string $key): void;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit\TimeProvider;
final readonly class SystemTimeProvider implements TimeProviderInterface
{
public function getCurrentTime(): int
{
return time();
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit\TimeProvider;
interface TimeProviderInterface
{
public function getCurrentTime(): int;
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Framework\RateLimit;
final readonly class TokenBucket
{
public function __construct(
public int $capacity,
public int $tokens,
public int $lastRefill
) {}
public function canConsume(int $tokens): bool
{
return $this->tokens >= $tokens;
}
public function consume(int $tokens): self
{
if (!$this->canConsume($tokens)) {
throw new \InvalidArgumentException('Not enough tokens available');
}
return new self(
$this->capacity,
$this->tokens - $tokens,
$this->lastRefill
);
}
public function refill(int $tokens, int $timestamp): self
{
$newTokens = min($this->capacity, $this->tokens + $tokens);
return new self(
$this->capacity,
$newTokens,
$timestamp
);
}
}