On Thu, Jul 11, 2024, at 01:11, Benjamin Morel wrote: >> The answer is: it depends. If you don’t need the array to clean up after >> itself, you can indeed use an array of WeakReference to get most of the way >> there. If you want it to clean up after an object gets removed, you either >> need to add support to the stored object’s destructor (which isn’t always >> possible for built-in or final types), or create your own garbage collector >> that scans the array. > > It is indeed doable in userland using WeakReferences, with a small > performance penalty: > > ``` > class ReverseWeakMap implements Countable, IteratorAggregate, ArrayAccess > { > /** > * @var array<int|string, WeakReference> > */ > private array $map = []; > > public function count(): int > { > foreach ($this->map as $value => $weakReference) { > if ($weakReference->get() === null) { > unset($this->map[$value]); > } > } > > return count($this->map); > } > > public function getIterator(): Generator > { > foreach ($this->map as $value => $weakReference) { > $object = $weakReference->get(); > > if ($object === null) { > unset($this->map[$value]); > } else { > yield $value => $object; > } > } > } > > public function offsetExists(mixed $offset) > { > if (isset($this->map[$offset])) { > $object = $this->map[$offset]->get(); > > if ($object !== null) { > return true; > } > > unset($this->map[$offset]); > } > > return false; > } > > public function offsetGet(mixed $offset): object > { > if (isset($this->map[$offset])) { > $object = $this->map[$offset]->get(); > > if ($object !== null) { > return $object; > } > > unset($this->map[$offset]); > } > > throw new Exception('Undefined offset'); > } > > public function offsetSet(mixed $offset, mixed $value): void > { > $this->map[$offset] = WeakReference::create($value); > } > > public function offsetUnset(mixed $offset): void > { > unset($this->map[$offset]); > } > } > ``` > >> Now that I think about it, it might be simpler to add an “onRemove()” method >> that takes a callback for the WeakReference class. >> >> — Rob > > > A callback when an object goes out of scope would be a great addition to both > WeakReference & WeakMap indeed, it would allow custom userland weak maps like > the above, with next to no performance penalty! > > - Benjamin >
The callback is surprisingly easy to implement, at least for WeakReference (did it in about 10 minutes on the train as a hack). I haven’t looked into WeakMap yet, but I suspect much of the plumbing is the same. I also looked into the ReverseWeakMap a bit and it seems there are just too many foorguns to make it worthwhile. For example: $reverseWeakMap[$key] = new Obj(); is actually a noop as in it does absolutely nothing. It gets worse, but I won’t bore you with the details since I won’t be doing it. Anyway, while I feel like the implementation for a callback will be extremely straightforward and the RFC rather simple, I need to go back and read the original discussion threads for this feature first to see if a callback was addressed. So, still not until after 8.4. — Rob