PHP 8.0: WeakMaps
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 theweakref
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 objects
s 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 serialize
d or unserialize
d. 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 WeakMap
in 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.