PHP
Token Bucket
Allow a short burst, then refill tokens at a steady rate. The burst cannot exceed the bucket size.
Reuse an idle connection inside the same process, instead of opening a new one for every query.
final class ConnectionPool
{
/** @var list<object> */
private array $idle = [];
/** @var array<int, object> */
private array $borrowed = [];
public function __construct(
private readonly int $max,
private readonly Closure $factory,
private readonly ?Closure $reset = null,
private readonly ?Closure $healthy = null,
) {
if ($max < 1) {
throw new InvalidArgumentException('Pool size must be at least 1.');
}
}
public function acquire(): object
{
while ($this->idle !== []) {
$connection = array_pop($this->idle);
if ($this->isHealthy($connection)) {
$this->borrowed[spl_object_id($connection)] = $connection;
return $connection;
}
}
if (count($this->borrowed) >= $this->max) {
throw new RuntimeException('Connection pool is exhausted.');
}
$connection = ($this->factory)();
$this->borrowed[spl_object_id($connection)] = $connection;
return $connection;
}
public function release(object $connection): void
{
$id = spl_object_id($connection);
if (!isset($this->borrowed[$id])) {
return;
}
unset($this->borrowed[$id]);
if ($this->reset) {
($this->reset)($connection);
}
if ($this->isHealthy($connection)) {
$this->idle[] = $connection;
}
}
public function using(callable $work): mixed
{
$connection = $this->acquire();
try {
return $work($connection);
} finally {
$this->release($connection);
}
}
public function stats(): array
{
return [
'idle' => count($this->idle),
'borrowed' => count($this->borrowed),
'max' => $this->max,
];
}
private function isHealthy(object $connection): bool
{
if ($this->healthy === null) {
return true;
}
try {
return (bool) ($this->healthy)($connection);
} catch (Throwable) {
return false;
}
}
}
$pool = new ConnectionPool(
4,
factory: static fn () => new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false,
]),
reset: static function (PDO $pdo): void {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
},
healthy: static function (PDO $pdo): bool {
$pdo->query('SELECT 1');
return true;
},
);
$email = 'ada@example.com';
$id = $pool->using(function (PDO $pdo) use ($email) {
$statement = $pdo->prepare('SELECT id FROM users WHERE email = ?');
$statement->execute([$email]);
return $statement->fetchColumn();
});
A practical place for this snippet in a real project.
Useful in a script or Artisan command that queries often in the same process. Short web requests usually do not need a hand-rolled pool if the server already manages connections.
Allow a short burst, then refill tokens at a steady rate. The burst cannot exceed the bucket size.
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.