WeakReference and WeakMap (PHP 8.0+) — preventing memory leaks
Concept
WeakReference and WeakMap (both PHP 8.0) provide ways to hold references to objects without preventing their garbage collection. They're essential tools for building caches, observer systems, and memoization that don't cause memory leaks in long-running applications.
WeakReference::create($object) creates a weak reference to an object. Calling $weakRef->get() returns the object if it's still alive, or null if it has been garbage collected. The WeakReference itself does not increment the object's refcount — the object can be freed even while the WeakReference exists.
WeakMap is a hash map where keys are objects and values are arbitrary data — but the key objects are held weakly. If an object used as a key is garbage collected, its entry is automatically removed from the WeakMap. This prevents the map from growing forever in a long-running process.
Use cases:
- Caching computed properties on objects: A
WeakMap<object, ComputedValue>stores computed results associated with objects; when the object is GC'd, the cache entry is automatically cleaned up. - Observer/event systems: Hold weak references to listeners. When a listener object is destroyed, it stops receiving events without needing explicit unregistration.
- Circular reference prevention: When two objects logically reference each other but you want one direction to be non-owning, use
WeakReferencefor that direction. - ORM identity map: An ORM's session can use a
WeakMapto track loaded entities. If the application discards the entity reference, the identity map entry is automatically removed.
Code Example
<?php
declare(strict_types=1);
// WeakReference — observe without owning
class HeavyObject
{
public string $data;
public function __construct(string $data) { $this->data = $data; }
public function __destruct() { echo "HeavyObject freed\n"; }
}
$obj = new HeavyObject(str_repeat('x', 1_000_000));
$weak = WeakReference::create($obj);
echo $weak->get()?->data[0]; // 'x' — object still alive
unset($obj); // refcount drops to 0 (WeakRef doesn't count)
// "HeavyObject freed" — destructor runs
var_dump($weak->get()); // NULL — object is gone
// WeakMap — cache that cleans itself up
$cache = new WeakMap();
class User { public function __construct(public string $name) {} }
class UserStats { public int $loginCount = 0; }
function getStats(User $user, WeakMap $cache): UserStats
{
if (!isset($cache[$user])) {
$cache[$user] = new UserStats(); // expensive computation
}
return $cache[$user];
}
$alice = new User('Alice');
$bob = new User('Bob');
$aliceStats = getStats($alice, $cache);
$aliceStats->loginCount = 5;
echo count($cache); // 1
$bobStats = getStats($bob, $cache);
echo count($cache); // 2
unset($alice); // Alice is no longer referenced
echo count($cache); // 1 — Alice's entry automatically removed
// WeakReference in event listener system
class EventDispatcher
{
/** @var array<string, WeakReference[]> */
private array $listeners = [];
public function listen(string $event, object $listener): void
{
$this->listeners[$event][] = WeakReference::create($listener);
}
public function dispatch(string $event, array $data = []): void
{
foreach ($this->listeners[$event] ?? [] as $ref) {
$listener = $ref->get();
if ($listener !== null) {
$listener->handle($data);
}
// If $listener is null, the listener was GC'd — silently skip
}
}
}