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
- Object keys: only objects can be used as keys.
- Weak references: a key in a
WeakMapdoes not keep the object alive. - Automatic removal: when a key object is destroyed, its entry disappears from the map.
- Standard interfaces:
WeakMapimplementsArrayAccess,CountableandIteratorAggregate, so it works with[],countandforeach.
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.
<?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.
<?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.
<?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.
<?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.
<?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.
<?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.
<?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.
<?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.
<?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.
<?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
In this article, we have shown how to use WeakMap in PHP to
associate data with objects without affecting their lifecycle.
Author
List all PHP tutorials.