Library

Connection Pool

Reuse an idle connection inside the same process, instead of opening a new one for every query.

PHP Free to use
Code
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;
        }
    }
}
Quick try
$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();
});

What it does

  • The pool lives in one PHP process. FPM workers do not share it.
  • Do not combine it with persistent PDO connections. If it is full, it fails immediately instead of sleeping the worker.
  • using returns the connection to the pool even when work throws. A dead connection is dropped on the health check.

Where it fits

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.

Similar snippets

Token Bucket
PHP

Token Bucket

Allow a short burst, then refill tokens at a steady rate. The burst cannot exceed the bucket size.

Password Hashing
PHP

Password Hashing

Store a password as a salted hash, with a pepper from app config. This is not reversible encryption.

Need a snippet that is not here?

Describe the problem or idea, and I will see if it belongs in the library.