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

Reply via email to