ZetCode

PHP WeakMap

last modified October 22, 2026

In this article, we explore the WeakMap class in PHP. A WeakMap is a map that uses objects as keys without preventing them from being garbage collected. It was introduced in PHP 8.0 and is useful for associating data with objects without causing memory leaks.

Main features of PHP WeakMap

PHP frees most objects through reference counting, as soon as the last reference to them disappears. This is why the entries in the following examples vanish immediately after unset.

Basic usage of WeakMap

The following example stores session data with a WeakMap.

basic.php
<?php

declare(strict_types=1);

$sessionCache = new WeakMap();

$userSession = new stdClass();
$userSession->id = "sess123";

$sessionCache[$userSession] = ["last_login" => "2026-10-22"];

var_dump(isset($sessionCache[$userSession]));
print_r($sessionCache[$userSession]);
echo count($sessionCache), "\n";

The session object acts as the key and an array with metadata is the value. Entries are written and read with the array syntax; isset checks whether an object has an entry.

$ php basic.php
bool(true)
Array
(
    [last_login] => 2026-10-22
)
1

Garbage collection with WeakMap

This example shows that an entry is removed when its key object is destroyed.

gc.php
<?php

declare(strict_types=1);

final class Connection
{
    public function __construct(public string $id) {}
}

$connections = new WeakMap();

$conn = new Connection("conn1");
$connections[$conn] = ["opened" => "08:15:00"];

echo "Entries: ", count($connections), "\n";

unset($conn);

echo "Entries after unset: ", count($connections), "\n";

After unset($conn), no reference to the connection object remains. The object is destroyed and the WeakMap drops its entry automatically, without any cleanup code.

$ php gc.php
Entries: 1
Entries after unset: 0

WeakMap versus SplObjectStorage

SplObjectStorage also maps objects to data, but it holds strong references.

array_vs_weakmap.php
<?php

declare(strict_types=1);

$storage = new SplObjectStorage();
$weak = new WeakMap();

$a = new stdClass();
$b = new stdClass();

$storage[$a] = 'metadata';
$weak[$b] = 'metadata';

unset($a, $b);

echo "SplObjectStorage: ", count($storage), "\n";
echo "WeakMap: ", count($weak), "\n";

After both variables are unset, SplObjectStorage still contains its object, which stays in memory as long as the storage exists. The WeakMap entry is gone. A plain array cannot be used here at all, because array keys must be integers or strings.

$ php array_vs_weakmap.php
SplObjectStorage: 1
WeakMap: 0

Iterating over a WeakMap

This example manages log entries tied to request objects.

multiple.php
<?php

declare(strict_types=1);

$logMap = new WeakMap();

$request1 = new stdClass();
$request1->id = "req1";
$request2 = new stdClass();
$request2->id = "req2";

$logMap[$request1] = ["log" => "Request 1 started"];
$logMap[$request2] = ["log" => "Request 2 started"];

foreach ($logMap as $request => $data) {
    echo "$request->id: {$data['log']}\n";
}

unset($request1);

echo "After unset:\n";

foreach ($logMap as $request => $data) {
    echo "$request->id: {$data['log']}\n";
}

In a foreach loop, the key variable receives the object itself. After the first request is unset, only the second request remains in the map.

$ php multiple.php
req1: Request 1 started
req2: Request 2 started
After unset:
req2: Request 2 started

Caching computed values

The most common use of WeakMap is a cache of values computed from objects.

cache.php
<?php

declare(strict_types=1);

final class Document
{
    public function __construct(public string $text) {}
}

final class WordCounter
{
    private WeakMap $cache;
    public int $computations = 0;

    public function __construct()
    {
        $this->cache = new WeakMap();
    }

    public function count(Document $doc): int
    {
        return $this->cache[$doc] ??= $this->compute($doc);
    }

    private function compute(Document $doc): int
    {
        $this->computations++;

        return str_word_count($doc->text);
    }
}

$counter = new WordCounter();

$doc1 = new Document("an old falcon soared over the valley");
$doc2 = new Document("the sky is blue");

echo $counter->count($doc1), "\n";
echo $counter->count($doc2), "\n";
echo $counter->count($doc1), "\n";

echo "Computations: $counter->computations\n";

The count method computes the number of words only once per document. The ??= operator assigns the computed value when the document has no entry yet. When a document object is destroyed, its cached value is released as well, so the cache cannot grow without bounds.

$ php cache.php
7
4
7
Computations: 2

WeakMap with custom objects

This example stores permissions for User objects.

custom_objects.php
<?php

declare(strict_types=1);

final class User
{
    public function __construct(public string $name) {}
}

$permissions = new WeakMap();

$jane = new User("Jane Doe");
$paul = new User("Paul Smith");

$permissions[$jane] = ["role" => "admin", "access" => ["read", "write"]];
$permissions[$paul] = ["role" => "guest", "access" => ["read"]];

foreach ($permissions as $user => $perm) {
    printf("%-10s %-6s %s\n", $user->name, $perm['role'], implode(', ', $perm['access']));
}

$permissions->offsetUnset($paul);
echo "Users with permissions: ", count($permissions), "\n";

The values can be any PHP values, here arrays. An entry can also be removed explicitly with offsetUnset, which is what unset($permissions[$paul]) does as well.

$ php custom_objects.php
Jane Doe   admin  read, write
Paul Smith guest  read
Users with permissions: 1

Tracking objects in a registry

This example uses a WeakMap inside a class to track open connections.

pool.php
<?php

declare(strict_types=1);

final class DbConnection
{
    public function __construct(public string $dsn) {}
}

final class ConnectionRegistry
{
    private WeakMap $meta;

    public function __construct()
    {
        $this->meta = new WeakMap();
    }

    public function open(string $dsn): DbConnection
    {
        $conn = new DbConnection($dsn);
        $this->meta[$conn] = ["opened" => hrtime(true)];

        return $conn;
    }

    public function active(): int
    {
        return count($this->meta);
    }
}

$registry = new ConnectionRegistry();

$conn1 = $registry->open("sqlite::memory:");
$conn2 = $registry->open("sqlite:/tmp/test.db");

echo "Active connections: ", $registry->active(), "\n";

$conn2 = null;

echo "Active connections: ", $registry->active(), "\n";

The registry knows how many connections are in use without owning them. When the caller drops $conn2, the registry's count goes down automatically. A registry built on a plain array would keep every connection alive forever.

$ php pool.php
Active connections: 2
Active connections: 1

Invalid keys

Only objects can be used as keys, and reading a missing key is an error.

invalid_key.php
<?php

declare(strict_types=1);

$map = new WeakMap();

try {
    $map["key"] = "value";
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}

try {
    echo $map[new stdClass()];
} catch (Error $e) {
    echo $e->getMessage(), "\n";
}

A string key raises a TypeError. Reading an object that is not in the map throws an Error; use isset or the ?? operator to read optional entries.

$ php invalid_key.php
WeakMap key must be an object
Object stdClass#3 not contained in WeakMap

Values that reference their key

If a value refers back to its key, the object is part of a reference cycle.

cycle.php
<?php

declare(strict_types=1);

$map = new WeakMap();

$node = new stdClass();
$map[$node] = ['owner' => $node];

unset($node);

echo "Before gc_collect_cycles: ", count($map), "\n";
gc_collect_cycles();
echo "After gc_collect_cycles: ", count($map), "\n";

The value holds a strong reference to the key object, so reference counting alone cannot free it. The cycle collector, which runs periodically or on a call to gc_collect_cycles, recognizes the cycle and removes the entry.

$ php cycle.php
Before gc_collect_cycles: 1
After gc_collect_cycles: 0

WeakReference

For a single object, PHP offers the WeakReference class, which WeakMap complements.

weakreference.php
<?php

declare(strict_types=1);

$obj = new stdClass();
$obj->name = "temporary";

$ref = WeakReference::create($obj);

var_dump($ref->get()?->name);

unset($obj);

var_dump($ref->get());

The get method returns the object while it exists and null after it has been destroyed.

$ php weakreference.php
string(9) "temporary"
NULL

Source

PHP WeakMap - Documentation

In this article, we have shown how to use WeakMap in PHP to associate data with objects without affecting their lifecycle.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP tutorials.