PHP
Connection Pool
Reuse an idle connection inside the same process, instead of opening a new one for every query.
Allow a short burst, then refill tokens at a steady rate. The burst cannot exceed the bucket size.
final class TokenBucketLimiter
{
/** @var array<string, array{tokens: float, updated_at: float}> */
private array $buckets = [];
public function __construct(
private readonly int $capacity,
private readonly float $refillPerSecond,
) {
if ($capacity < 1 || $refillPerSecond <= 0) {
throw new InvalidArgumentException('Capacity and refill rate must be positive.');
}
}
public function allow(string $key, int $cost = 1): bool
{
return $this->take($key, $cost)['allowed'];
}
/**
* @return array{allowed: bool, remaining: int, retry_after: int}
*/
public function take(string $key, int $cost = 1): array
{
if ($cost < 1) {
throw new InvalidArgumentException('Cost must be at least 1.');
}
$now = microtime(true);
$bucket = $this->refill($this->buckets[$key] ?? $this->full($now), $now);
if ($cost > $this->capacity) {
$this->buckets[$key] = $bucket;
return [
'allowed' => false,
'remaining' => (int) floor($bucket['tokens']),
'retry_after' => 0,
];
}
$allowed = $bucket['tokens'] >= $cost;
if ($allowed) {
$bucket['tokens'] -= $cost;
}
$this->buckets[$key] = $bucket;
$missing = $allowed ? 0.0 : ($cost - $bucket['tokens']);
return [
'allowed' => $allowed,
'remaining' => (int) floor($bucket['tokens']),
'retry_after' => $missing <= 0 ? 0 : (int) ceil($missing / $this->refillPerSecond),
];
}
public function remaining(string $key): int
{
$now = microtime(true);
$bucket = $this->refill($this->buckets[$key] ?? $this->full($now), $now);
$this->buckets[$key] = $bucket;
return (int) floor($bucket['tokens']);
}
/** @param array{tokens: float, updated_at: float} $bucket */
private function refill(array $bucket, float $now): array
{
$elapsed = max(0.0, $now - $bucket['updated_at']);
$bucket['tokens'] = min(
(float) $this->capacity,
$bucket['tokens'] + ($elapsed * $this->refillPerSecond)
);
$bucket['updated_at'] = $now;
return $bucket;
}
/** @return array{tokens: float, updated_at: float} */
private function full(float $now): array
{
return [
'tokens' => (float) $this->capacity,
'updated_at' => $now,
];
}
}
$limiter = new TokenBucketLimiter(5, 1); // burst 5, then 1 token / second
$first = $limiter->take('user:42');
// ['allowed' => true, 'remaining' => 4, 'retry_after' => 0]
for ($i = 0; $i < 5; $i++) {
$limiter->take('user:42');
}
$blocked = $limiter->take('user:42');
// ['allowed' => false, 'remaining' => 0, 'retry_after' => 1]
echo $limiter->remaining('user:42');
A practical place for this snippet in a real project.
Useful for a form endpoint or an API route inside the same process. It is not a front-door limit for every server.
Reuse an idle connection inside the same process, instead of opening a new one for every query.
Store a password as a salted hash, with a pepper from app config. This is not reversible encryption.
Describe the problem or idea, and I will see if it belongs in the library.