PHP 8.0: WeakMaps

Version8.0
TypeNew Feature

Weak Maps is a new feature added in PHP 8.0.

A WeakMap allows to store and associate arbitrary values for object keys, but without preventing the garbage collector from clearing the object if it falls out of scope in everywhere else.

Weak Maps in PHP 8.0 is different from the WeakMap class from the weakref PECL extension. See PHP WeakMap Practical Use Cases: PECL extension for more information.

A WeakMap is similar to SplObjectStorage, as in both WeakMap and splObjectStorage use objectss as the key, and allows storage of arbitrary values. However, a WeakMap does not prevent the object from being garbage collected.

$map = new splObjectStorage();

$object = new stdClass();
$map[$object]  = 'Foo';

var_dump(count($map)); // int(1)

unset($object);

var_dump(count($map)); // int(1)

In the snippet above, splObjectStorage is used to store additional data about objects. Even though there is an unset($object) call, PHP's garbage collector does not clear it from memory because of the reference within the $map splObjectStorage.

With WeakMap, the references are weak, and the reference inside the WeakMap does not prevent the garbage collector from clearing the object if there are no other references to the object.

- $map = new splObjectStorage();
+ $map = new WeakMap();

$object = new stdClass();
$map[$object]  = 'Foo';

var_dump(count($map)); // int(1)

unset($object);

var_dump(count($map)); // int(0)

A more simplified example would be:

$map = new splObjectStorage();
$map[new stdClass()] = 'Foo';
count($map); // 1

$map = new WeakMap();
$map[new stdClass()] = 'Foo';
count($map); // 0

new stdClass is used as the key, and it immediately falls out of scope. WeakMap allows the object to be cleared, while splObjectStorage does not.

Weak Maps can help associate transient data for objects without changing the state of the object itself. If the object falls out of scope, so does the associated data.


Complete Example

class CommentRepository {
    private WeakMap $comments_cache;
    public function getCommentsByPost(Post $post): ?array {
        if (!isset($this->comments_cache[$post])) {
            $this->comments_cache[$post] = $this->loadComments($post);
        }
        return $this->comments_cache[$post]
    }
}

In the example above, the CommentRepository class uses WeakMap class to temporarily store comments in memory, stored as typed property $comments_cache. When the getCommentsByPost method is called with a Post object, the $comments_cache is checked and returned first, before loading them from the database with private loadComments method.

If the CommentRepository class were to store the comments in array (indexed by the post ID), or in SplObjectStorage, this cache will not be cleared even if the Post object falls out of scope or even explicitly unset with unset($post).

With help from WeakMap, CommentRepository class can now cache data (array of comments) without "holding onto" the Post objects, but still associate the comments with Post objects. The Post object state is not changed (e.g. by setting $post->comments property), and the life-cycle of the comments managed automatically alongside the garbage clearance of the Post object itself.


WeakMap class synopsis

final class WeakMap implements ArrayAccess, Countable, IteratorAggregate, Traversable {
    public function offsetGet(object $object);
    public function offsetSet(object $object, mixed $value): void;
    public function offsetExists(object $object): bool;
    public function offsetUnset(object $object): void;
    public function count(): int;
}

WeakMap keys must be objects

Weak Maps only allow objects as keys, and the associated allows arbitrary values.

Attempting to store any other data type as the key will throw a type error.

$map = new WeakMap();
$map['Foo'] = 'Bar';

// Fatal error: Uncaught TypeError: WeakMap key must be an object in ...:...

Appending to a WeakMap is not allowed either.

$map = new WeakMap();
$map[] = 'Baz';

// Fatal error: Uncaught Error: Cannot append to WeakMap in ...:...

Error on non-existing keys

If the requested object does not exist in the weakmap object, an \Error exception is thrown.

$map = new WeakMap();
$map[new stdClass()];

// Fatal error: Uncaught Error: Object stdClass#2 not contained in WeakMap in ...:...

This can be avoided by checking if the index exists in the map.

$map = new WeakMap();
isset($map[new stdClass()]); // false

WeakMap does not allow properties

$map = new WeakMap();
$map->foo = 'Bar';

// Fatal error: Uncaught Error: Cannot create dynamic property WeakMap::$foo in ...:...

WeakMap does not support Serialization/Unserialization

Weak Maps are not allowed to be serialized or unserialized. Given the WeakMap class is declared final, this can be changed either.

$map = new WeakMap();
serialize($map);

// Fatal error: Uncaught Exception: Serialization of 'WeakMap' is not allowed in ...:...
$serialized_str = 'C:7:"WeakMap":0:{}';
unserialize($serialized_str);

// Fatal error: Uncaught Exception: Unserialization of 'WeakMap' is not allowed in ...:...

Iterating Weak Maps

WeakMap class implements Traversable interface, it can be used iterated with foreach construct.

Further, WeakMap implements IteratorAggregate that brings a WeakMap::getIterator method.

$map = new WeakMap();

$obj1 = new stdClass();
$map[$obj1] = 'Object 1';

foreach ($map as $key => $value) {
    var_dump($key); // var_dump($obj1)
    var_dump($value); // var_dump('Object 1');
}

The iterator itself can be extracted with getIterator method, and the return value is an iterable.

$map = new WeakMap();

$obj1 = new stdClass();
$map[$obj1] = 'Object 1';

$iterator = $map->getIterator();

foreach ($iterator as $key => $value) {
    var_dump($key); // var_dump($obj1)
    var_dump($value); // var_dump('Object 1');
}

Practical Use Cases

Weak Maps can help with temporary storage of objects, but without affecting the memory usage due to the temporary storage itself.

When used as a cache storage, the object keys can be used to store a primitive state of the object, and reduce subsequent calls to an expensive function that is supposed to process or enhance the object.

Another use case would be storage of completion data for a collection of objects, but without mutating the object itself.

For more examples of Weak Maps in PHP 8.0) along with code examples, see PHP WeakMap Practical Use Cases.

Backwards Compatibility Impact

Weak Maps is a new feature in PHP 8.0, and unless there is a class with name WeakMapin the global namespace, there should not be any issues when upgrading an existing code base to PHP 8.0.

Because the WeakMap is an internal class, this functionality cannot be back-ported to prior PHP versions and make it work with the native garbage collector. A user-land implementation exists, but at a significant performance penalty.

A PECL extension named weakref provided similar functionality, but it is no longer maintained.


RFC Discussion Implementation