*/ private array $objects = []; private function __construct() { } /** * @return ArrayAccess&Countable&IteratorAggregate */ public static function create(): ArrayAccess { if (PHP_VERSION_ID >= 80000) { /** @phan-suppress-next-line PhanUndeclaredClassReference */ assert(class_exists(\WeakMap::class, false)); /** @phan-suppress-next-line PhanUndeclaredClassMethod */ $map = new \WeakMap(); assert($map instanceof ArrayAccess); assert($map instanceof Countable); assert($map instanceof IteratorAggregate); return $map; } return new self(); } public function offsetExists($offset): bool { if (!is_object($offset)) { throw new TypeError('WeakMap key must be an object'); } return isset($offset->{self::KEY}[spl_object_id($this)]); } /** * @phan-suppress PhanUndeclaredClassAttribute */ #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!is_object($offset)) { throw new TypeError('WeakMap key must be an object'); } if (!$this->contains($offset)) { throw new Error(sprintf('Object %s#%d not contained in WeakMap', get_class($offset), spl_object_id($offset))); } return $offset->{self::KEY}[spl_object_id($this)]; } public function offsetSet($offset, $value): void { if ($offset === null) { throw new Error('Cannot append to WeakMap'); } if (!is_object($offset)) { throw new TypeError('WeakMap key must be an object'); } if (!$this->contains($offset)) { $this->expunge(); } $offset->{self::KEY}[spl_object_id($this)] = $value; $this->objects[spl_object_id($offset)] = WeakReference::create($offset); } public function offsetUnset($offset): void { if (!is_object($offset)) { throw new TypeError('WeakMap key must be an object'); } if (!$this->contains($offset)) { return; } unset( $offset->{self::KEY}[spl_object_id($this)], $this->objects[spl_object_id($offset)], ); if (!$offset->{self::KEY}) { unset($offset->{self::KEY}); } } public function count(): int { $this->expunge(); return count($this->objects); } public function getIterator(): Traversable { $this->expunge(); foreach ($this->objects as $reference) { if (($object = $reference->get()) && $this->contains($object)) { yield $object => $this[$object]; } } } public function __debugInfo(): array { $debugInfo = []; foreach ($this as $key => $value) { $debugInfo[] = ['key' => $key, 'value' => $value]; } return $debugInfo; } public function __destruct() { foreach ($this->objects as $reference) { if ($object = $reference->get()) { unset($this[$object]); } } } private function contains(object $offset): bool { $reference = $this->objects[spl_object_id($offset)] ?? null; if ($reference && $reference->get() === $offset) { return true; } unset($this->objects[spl_object_id($offset)]); return false; } private function expunge(): void { foreach ($this->objects as $id => $reference) { if (!$reference->get()) { unset($this->objects[$id]); } } } }