Circular References & Memory Implications
debt(d7/e3/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The term's detection_hints list xdebug and blackfire as tools, but automated detection is explicitly marked 'no'. Circular references don't cause compiler or linter errors; they require profiling under load or deliberate memory-growth testing, meaning they're typically found only through runtime investigation or careful code review — not automatically surfaced.
Closest to 'simple parameterised fix' (e3). The quick_fix calls for replacing back-references with WeakReference and adding gc_collect_cycles() calls in long-running loops. This is a targeted refactor — swapping direct references for WeakReference wrappers within the affected object graph — touching a focused set of files rather than a single one-liner, but not a broad cross-cutting refactor.
Closest to 'persistent productivity tax' (b5). The applies_to scope is cli and queue-worker contexts, not all PHP. However, in those long-running contexts every object graph with parent/back-references must be reviewed and potentially rewritten, and memory monitoring must be embedded into operational practice. It doesn't shape the whole system but imposes ongoing discipline on a significant subset of workstreams.
Closest to 'serious trap' (t7). The misconception field states exactly: developers assume PHP's GC immediately frees objects when they go out of scope, but circular references defer collection to the cycle collector. This contradicts the mental model most PHP developers carry from typical short-lived web requests, and the failure mode (gradual memory growth in long-running processes) is invisible in standard web contexts — a meaningful behavioral contradiction from how the concept appears to work.
TL;DR
Explanation
PHP uses reference counting for memory management. When object A holds a reference to B and B holds one back to A, neither reaches zero refcount and they leak until the cycle collector runs. PHP's cycle collector (introduced in 5.3) detects and cleans cycles but runs periodically, not immediately. Common patterns: parent/child trees, observers holding back-references, doubly-linked lists. Avoid by: using weak references (WeakReference in PHP 7.4+), breaking cycles explicitly (setting $child->parent = null before unset), or restructuring to avoid bidirectional references.
Common Misconception
Why It Matters
Common Mistakes
- Building object trees with parent references in long-running processes without cleanup.
- Not using WeakReference for back-references in observer patterns.
- Not calling gc_collect_cycles() manually when memory grows unexpectedly.
Code Examples
class Node {
public ?Node $parent = null;
public array $children = [];
public function addChild(Node $child): void {
$child->parent = $this; // Circular reference
$this->children[] = $child;
}
}
class Node {
private ?\WeakReference $parent = null;
public array $children = [];
public function setParent(Node $parent): void {
$this->parent = \WeakReference::create($parent);
}
public function getParent(): ?Node {
return $this->parent?->get();
}
}