Object Pooling
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no' and the only listed tool is Blackfire (a profiler), meaning the absence of pooling — or misuse of it (dirty state, wrong pool sizing) — only surfaces through profiling or careful review of long-running worker code. It won't show up in linting or static analysis.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix clarifies this is context-dependent (PHP-FPM vs Swoole/RoadRunner), and properly implementing pooling requires introducing a pool manager, updating every site that creates the expensive object, ensuring state reset logic, and handling exception paths — spanning multiple files and components within a worker.
Closest to 'persistent productivity tax' (e5 → b5). The applies_to covers web, cli, and queue-worker contexts. Once a pool is introduced in a long-running worker, every developer touching that code must understand pool lifecycle, state reset contracts, and exception handling to avoid dirty-object bugs. It imposes an ongoing mental and structural tax on those work streams without necessarily reaching architectural scope.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field directly states the canonical wrong belief: developers assume object pooling is irrelevant in PHP because PHP-FPM resets state per request. This leads to either ignoring pooling entirely in Swoole/RoadRunner workers (missing performance gains) or, conversely, incorrectly applying pooling inside a PHP-FPM request where it's worthless. The 'dirty object' mistake also means the obvious implementation (return object to pool immediately) is silently wrong if state isn't reset.
Also Known As
TL;DR
Explanation
Object pooling maintains a pool of reusable objects (database connections, HTTP clients, worker threads) that are checked out, used, and returned rather than instantiated fresh each time. In traditional PHP request-per-process model, object pools within a single request have limited value — the process dies anyway. They become highly relevant in long-running PHP contexts: Swoole coroutines, RoadRunner workers, or CLI queue workers that handle many tasks. Database connection pools (pgBouncer, ProxySQL) operate at the infrastructure level and benefit all PHP architectures. In-process pools in PHP-FPM should be evaluated carefully — memory is not shared between workers.
Common Misconception
Why It Matters
Common Mistakes
- Not resetting object state before returning to pool — a 'dirty' object from one request affects the next.
- Pool size not matched to concurrency — too small causes wait queues; too large wastes memory.
- Pooling cheap objects — the pool management overhead exceeds the construction savings for simple objects.
- Not handling exceptions that leave an object in an invalid state — the broken object must not return to the pool.
Code Examples
// Creating a new HTTP client on every request — expensive:
function callApi(string $url): array {
$client = new GuzzleHttp\Client(['timeout' => 5]); // New client each call
return $client->get($url)->json();
// Guzzle clients are reusable — inject a shared instance instead
}
// Reuse expensive objects rather than constructing and destroying repeatedly
class PdfRendererPool {
private \SplStack $pool;
public function __construct(private int $size = 4) {
$this->pool = new \SplStack();
for ($i = 0; $i < $size; $i++) {
$this->pool->push(new PdfRenderer()); // pre-warm
}
}
public function acquire(): PdfRenderer {
return $this->pool->isEmpty()
? new PdfRenderer() // overflow — create temporary
: $this->pool->pop();
}
public function release(PdfRenderer $r): void {
if ($this->pool->count() < $this->size) {
$this->pool->push($r); // return to pool
}
// else let it GC — pool is full
}
}