A memoisation cache keyed on object identity keeps every object it has ever seen alive, which in a long-running process is a leak with no upper bound.
final class Prices
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap();
}
public function for(Product $p): Money
{
return $this->cache[$p] ??= $this->calculate($p);
}
}
// when $p is garbage collected, the entry goes with it.
// SplObjectStorage would have kept both forever.
The distinction from SplObjectStorage is that the key is a weak reference, so the map does not count as a reason to keep the object alive. That only matters in a process that outlives a request — a worker, a long import, an application server keeping the framework in memory — and in a normal web request the difference is invisible because everything is freed anyway. Keys must be objects; there is no scalar-keyed equivalent.