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
WeakMap
class 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_cache
snippet 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
array
is used for the cache storage, the key must be astring
or anint
, which requires a unique key inside thePost
object. - If
splObjectStorage
is used for the cache storage, they will not be removed when thePost
object 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
Account
objects. - The
EmailSender::getStats
method returns aniterable
to use withforeach
, and can list the number of each account is emailed. - If an
Account
object falls out of scope of the parent caller, orunset
anywhere 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.