PHP 8 Weak Maps and Practical Use Cases

PHP 8.0 brings Weak Maps to PHP. Weak Maps are data structures that can hold objects, but allows them to be cleared with garbage collection.
A post with the
WeakMapclass synopsis, restrictions, and its semantic is at PHP 8.0: Weak Maps.
Weak Maps takes the PHP 7.4 Weak References to a new level, and introduces collections of references that can be garbage collected if there are no other references to the object. Weak Maps can help minimize and efficiently manage memory.
Most of the PHP applications are request-based, and memory is automatically cleared at the end of the request. Async PHP approaches, and long-lived applications, however, need to keep an eye on potential memory leaks. WeakMap class instances can work as a clever and easy work-around to the object life-cycle management.
How Weak Maps Work
Weak Maps basically function as a key-value store, where keys are objects, and values can be arbitrary data. The key is weakly referenced in the key-value store, and if the key falls out of scope, the weak map key-value store does not prevent the key object from being garbage collected.
A Weak Map store additional data against the object key, without changing the state of the key object itself, or objecting its garbage collection.
PHP's splObjectStorage class allows to store additional data for an object key.
$obj1 = new stdClass();
$obj->name = 'Foo'; $obj->age = 28;
$map = new splObjectStorage();
$map[$obj1] = 'Additional data for stdClass #1';
The key is strongly referenced, and prevents them from being garbage collected.
If the $obj1 falls out of scope, for example they are being unset($obj1), PHP's garbage collector cannot clean those objects because they are still referenced in the $map splObjectStorage object.
$obj1 = new stdClass();
$obj->name = 'Foo'; $obj->age = 28;
$map = new splObjectStorage();
$map[$obj1] = 'Additional data for stdClass #1';
unset($obj1); // Only clears reference
count($map); // 1
Weak Maps is a key-value store similar to splObjectStorage, where keys are objects. However, the references are weak, as in they allow the garbage collector to clear out the values.
$obj1 = new stdClass();
$obj->name = 'Foo'; $obj->age = 28;
$map = new WeakMap();
$map[$obj1] = 'Additional data for stdClass #1';
The benefit of Weak Map is that, if the object falls out of scope, the garbage collector can immediately remove it from the Weak Map. When the key object is removed (garbage collected), its associated value will be removed from the Weak Map as well.
$obj1 = new stdClass();
$obj->name = 'Foo'; $obj->age = 28;
$map = new WeakMap();
$map[$obj1] = 'Additional data for stdClass #1';
unset($obj1); // Removes $obj and the key and value from $map as well.
count($map); // 0
Practical Use Cases of PHP Weak Maps
Weak Maps use objects as keys. A Weak Map might be an ideal candidate to store associated data for a given object. When the object is unset, or falls out of scope, Weak Map makes sure that the additional data is cleared too.
This provides a volatile cache storage without Lapsed listener problems, that the cached data does not cause a memory leak either by storing the objects preventing the garbage collector, and by storing additional data for objects no longer necessary.
In Caching
Weak Maps help create temporary cache, where the life-cycle of the cache keys (objects) and values (arbitrary data) is managed automatically when the key falls out of scope.
The following example is for a sample CommentRepository class, that is responsible to load comments of a given Post object.
CommentRepository can make use of a Weak Map to store the cached data, and prevent database calls if the comments are already retrieved for the given post.
class CommentRepository {
private WeakMap $comments_cache;
public function __construct() {
$this->comments_cache = new WeakMap();
}
public function getCommentsByPost(Post $post): ?array {
if (!isset($this->comments_cache[$post])) {
$this->comments_cache[$post] = $this->loadComments($post);
}
return $this->comments_cache[$post]
}
}
private WeakMap $comments_cachesnippet is using PHP 7.4 typed properties.
$comments_cache property stores comments key'd by the Post objects. This reference does not count when PHP garbage collector attempts to clear a Post object. When a Post object is cleared, all its associated comment items (values) will be automatically removed as well.
- If an
arrayis used for the cache storage, the key must be astringor anint, which requires a unique key inside thePostobject. - If
splObjectStorageis used for the cache storage, they will not be removed when thePostobject is unset.
A few calls to load comments might not make a noticeable difference. However, if the CommentRepository::getCommentsByPost is called several times, a strongly referenced cache would have caused a memory leak due to the strong reference to each Post object and its loaded values.
With a Weak Map, only the alive Post objects and their comments are stored in the map.
In associating auxiliary object data
Another use case of Weak Maps is storing auxiliary data for objects without storing them in the object itself.
Due to the weak references in a Weak Map, it is possible to associate auxiliary data for an object, and have them removed automatically when the object is unset.
class EmailSender {
private WeakMap $counter;
public function __construct() {
$this->counter = new WeakMap();
}
public function send(Account $account, EmailMessage $message) {
$this->counter[$account] ??= 0;
$this->counter[$account]++;
// Do the rest.
}
public function getStats(): iterable {
return $this->counter->getIterator();
}
}
$this->counter[$account] ??= 0;snippet is using the Null Coalescing Assignment operator
With the example above, every-time the EmailSender::send method is called, it counts the number of times an email is sent to each Account object.
- The number of emails sent does not belong as a property to the
Accountobjects. - The
EmailSender::getStatsmethod returns aniterableto use withforeach, and can list the number of each account is emailed. - If an
Accountobject falls out of scope of the parent caller, orunsetanywhere in the code, the counter will be removed as well.
WeakMap objects support iterating with foreach, or retrieving an iterable object from the getIterator method.


