<?php declare(strict_types=1); /** * \WeakMap with https://github.com/php/php-src/issues/10043 fixed for PHP 8.2 and below. * * @template TKey of object * @template TValue of mixed */ final class WeakMapPhp83 implements \ArrayAccess, \Countable, \IteratorAggregate { /** Workaround https://github.com/php/php-src/issues/13612. */ private \stdClass $destructedEarly; /** @var \WeakMap<TKey, \WeakReference<WeakMapPhp83Value<TKey, TValue>>> */ private \WeakMap $weakMap; public function __construct() { $this->weakMap = new \WeakMap(); $this->destructedEarly = new \stdClass(); } /** * @param TKey $object */ public function offsetExists($object) : bool { $this->assertValidKey($object); return $this->weakMap->offsetExists($object) && $this->offsetGet($object) !== null; } /** * @param TKey $object * * @return TValue */ #[\ReturnTypeWillChange] public function offsetGet($object) { $this->assertValidKey($object); return $this->weakMap->offsetGet($object)->get()->get(); } /** * @param TKey $object * @param TValue $value */ public function offsetSet($object, $value) : void { $this->assertValidKey($object); $valueBefore = null; if ($this->weakMap->offsetExists($object)) { $valueBefore = $this->weakMap->offsetGet($object)->get(); } $this->weakMap->offsetSet( $object, \WeakReference::create( new WeakMapPhp83Value($this->weakMap, $this->destructedEarly, $object, $value) ) ); if ($valueBefore !== null) { $valueBefore->destroy(); } } /** * @param TKey $object */ public function offsetUnset($object) : void { $this->assertValidKey($object); $valueBefore = null; if ($this->weakMap->offsetExists($object)) { $valueBefore = $this->weakMap->offsetGet($object)->get(); } $this->weakMap->offsetUnset($object); if ($valueBefore !== null) { $valueBefore->destroy(); } } public function count() : int { return $this->weakMap->count(); } /** * @return \Traversable<TKey, TValue> */ public function getIterator() : \Traversable { foreach ($this->weakMap->getIterator() as $object => $v) { yield $object => $this->offsetGet($object); } } private function assertValidKey($key) : void { if ($key === null) { throw new \Error('Cannot append to WeakMap'); } if (!is_object($key)) { throw new \TypeError('WeakMap key must be an object'); } } } /** * @internal * * @template TKey of object * @template TValue of mixed */ class WeakMapPhp83Value { /** @var \WeakReference<\WeakMap<TKey, \WeakReference<self<TKey, TValue>>>> */ private \WeakReference $weakMap; /** @var \WeakReference<\stdClass> */ private \WeakReference $weakMapDestructedEarly; /** @var \WeakReference<TKey> */ private \WeakReference $weakKey; /** @var TValue */ private $value; private ?\stdClass $cycleRef; /** * @param \WeakMap<TKey, \WeakReference<self<TKey, TValue>>> $weakMap * @param \stdClass $destructedEarly * @param TKey $key * @param TValue $value */ public function __construct(\WeakMap $weakMap, \stdClass $destructedEarly, object $key, $value) { $this->weakMap = \WeakReference::create($weakMap); $this->weakMapDestructedEarly = \WeakReference::create($destructedEarly); $this->weakKey = \WeakReference::create($key); $this->value = $value; // make self cyclically referenced to survive GC run, inspired by CycleWithDestructor $this->cycleRef = new \stdClass(); $this->cycleRef->x = $this; } public function __destruct() { if ($this->weakMapDestructedEarly->get() === null) { $this->destroy(); return; } $weakMap = $this->weakMap->get(); if ($weakMap === null) { $this->destroy(); return; } $key = $this->weakKey->get(); if ($key !== null && $weakMap->offsetExists($key)) { // set new value wrapper, as the previous one (=$this) will be released after GC run $weakMap->offsetSet( $key, \WeakReference::create( new self($weakMap, $this->weakMapDestructedEarly->get(), $key, $this->value) ) ); } else { $this->destroy(); } } /** * @return TValue */ public function get() { return $this->value; } public function destroy() : void { $this->value = null; $this->cycleRef = null; } } function testHousekeepingOnGcRunSurvival() : void { $weakMap = new WeakMapPhp83(); $vkPairs = []; for ($i = 20; $i > 0; $i--) { for ($j = 100; $j > 0; $j--) { $k = new stdClass; $v = new stdClass; $weakMap[$k] = $v; $vkPairs[] = [$k, $v]; } gc_collect_cycles(); gc_collect_cycles(); gc_collect_cycles(); } foreach ($vkPairs as [$k, $v]) { if ($weakMap[$k] !== $v) { throw new \Error(); } } } testHousekeepingOnGcRunSurvival(); echo 'done';
You have javascript disabled. You will not be able to edit any code.