Using WeakMap in PHP 8.0

The SplObjectStorage class allows to store additional data about objects. Objects are stored as keys and associated data as values. In other words, this class allows to create a map from objects to data. The objects that are used as keys are strongly referenced. This means if an object falls out of scope and is not used anywhere, a SplObjectStorage prevents the garbage collector to cleanup this object. This can cause a memory leak.

We created an instance of SplObjectStorage class. One object was added to the map. Object gets destructed by using unset function. However, garbage collector cannot cleanup this object because it still used in the SplObjectStorage.

<?php

$map = new SplObjectStorage();

$object = new stdClass();

$map[$object] = 'Data';
echo count($map); // 1

unset($object);
echo count($map); // 1

Since PHP 8.0, we can use WeakMap class that allows to create a map from objects to data like a SplObjectStorage does. However, the objects that are used as keys are weakly referenced. If an object goes out of scope, a WeakMap don’t prevent this object to be cleaned by garbage collector.

We created an instance of WeakMap class. When object gets destructed, it will be cleaned by garbage collector and removed from the WeakMap.

<?php

$map = new WeakMap();

$object = new stdClass();

$map[$object] = 'Data';
echo count($map); // 1

unset($object);
echo count($map); // 0

A WeakMap can be used to implement cache.

Let’s say we have two classes: Product and Supplier.

Product.php

<?php

class Product {}

Supplier.php

<?php

class Supplier {}

We created a ProductRepository class that has findProductsBySupplier method. This method returns products by supplier. If the products for the given supplier are already loaded, they are retrieved from the $cache property instead of loading from database or other storage. The getCacheSize method is used for testing to check cache size.

ProductRepository.php

<?php

class ProductRepository
{
    private WeakMap $cache;

    public function __construct()
    {
        $this->cache = new WeakMap();
    }

    public function getCacheSize(): int
    {
        return count($this->cache);
    }

    public function findProductsBySupplier(Supplier $supplier): array
    {
        return $this->cache[$supplier] ??= $this->loadProductsBySupplier($supplier);
    }

    private function loadProductsBySupplier(Supplier $supplier): array
    {
        // Method implementation ...

        return [
            new Product(),
            new Product(),
        ];
    }
}

When Supplier object is destructed, the garbage collector removes object key and all related products (values) from the cache.

<?php

require_once 'Product.php';
require_once 'Supplier.php';
require_once 'ProductRepository.php';

$repository = new ProductRepository();

$supplier = new Supplier();

$products = $repository->findProductsBySupplier($supplier);
echo $repository->getCacheSize(); // 1

unset($supplier);
echo $repository->getCacheSize(); // 0

Leave a Comment

Your email address will not be published. Required fields are marked *