Un WeakMap es una clase interna con la que se construye una estructura tipo diccionario en la que las claves son objetos y los valores pueden ser cualquier cosa.
WeakMap es una clase incorporada a PHP en la versión 8.0, si quieres saber mas te invito a leer la Super Guía de PHP Moderno
La diferencia frente a un array normal está en el tipo de referencia que se mantiene hacia esas claves: en un WeakMap la referencia es débil, así que no impide que el objeto sea destruido si ya no queda ninguna otra referencia fuerte hacia él en el resto del programa. Cuando eso sucede, la entrada correspondiente se elimina sola.
$mapa = new WeakMap(); $objeto = new stdClass(); $mapa[$objeto] = 'algún dato asociado'; var_dump(count($mapa)); // int(1) unset($objeto); // se elimina la única referencia fuerte var_dump(count($mapa)); // int(0), la entrada se limpió sola
El problema que venía de antes
En PHP, los objetos nunca se han podido usar directamente como clave de un array asociativo. Durante años, la solución habitual pasaba por dos caminos: generar un identificador con spl_object_hash() o spl_object_id() y usarlo como clave de un array, o recurrir a SplObjectStorage, pensada justamente para asociar datos a objetos.
El inconveniente de ambas soluciones era el mismo: en las dos se retiene una referencia fuerte al objeto. Mientras el array o el SplObjectStorage sigan vivos, el objeto usado como clave nunca es liberado por el recolector de basura, aunque en el resto de la aplicación ya no exista ninguna otra referencia hacia él.
$cache = new SplObjectStorage();
function procesar($objeto, SplObjectStorage $cache) {
$cache[$objeto] = 'resultado costoso de calcular';
}
$usuario = new stdClass();
procesar($usuario, $cache);
unset($usuario); // el objeto no se libera, sigue retenido dentro de $cache
Cuando una aplicación crea muchos objetos de vida corta y estos se van asociando a una caché de ese tipo, con el tiempo se acumulan objetos que nunca llegan a liberarse. Es una fuga de memoria silenciosa, difícil de detectar hasta que el proceso empieza a consumir memoria de forma anómala. Para ese escenario se diseñó WeakMap.
Comparación con las alternativas anteriores
| Característica | Array + spl_object_hash | SplObjectStorage | WeakMap |
|---|---|---|---|
| Objetos como clave | De forma indirecta (hash) | Sí | Sí |
| Tipo de referencia al objeto | Fuerte | Fuerte | Débil |
| Memoria liberada de forma automática | No | No | Sí |
| Riesgo de fuga de memoria | Alto | Alto | Bajo |
| Disponible desde | Siempre | PHP 5.3 | PHP 8.0 |
No se trata de que WeakMap sustituya a SplObjectStorage en todos los casos; existe para el escenario en el que se necesita asociar datos a un objeto sin alargar artificialmente su ciclo de vida.
Sintaxis y uso básico
WeakMap implementa las interfaces ArrayAccess, Countable e IteratorAggregate, de modo que se comporta de forma parecida a un array o a otras estructuras de la SPL.
$mapa = new WeakMap();
$objetoA = new stdClass();
$objetoB = new stdClass();
// Asignación de valores
$mapa[$objetoA] = 'metadato de A';
$mapa[$objetoB] = ['tipo' => 'usuario', 'activo' => true];
// Lectura de valores
echo $mapa[$objetoA]; // 'metadato de A'
// Comprobación de existencia
var_dump(isset($mapa[$objetoA])); // bool(true)
// Eliminación manual de una entrada
unset($mapa[$objetoA]);
// Conteo de entradas
echo count($mapa); // 1
// Iteración
foreach ($mapa as $objeto => $valor) {
echo get_class($objeto) . ': ' . print_r($valor, true);
}
Solo se admiten objetos como clave. Si se intenta usar un entero, un string o cualquier otro tipo escalar, se lanza un TypeError.
Dónde se aplica en la práctica
Resultados costosos asociados a un objeto. Cuando una función calcula algo pesado a partir de un objeto —un resumen generado a partir de un texto, un hash, datos derivados de una consulta—, ese resultado puede cachearse mientras el objeto exista, sin que la propia caché sea la razón por la que ese objeto nunca se destruye.
class CalculadorDeResumen
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap();
}
public function resumen(Documento $documento): string
{
if (!isset($this->cache[$documento])) {
$this->cache[$documento] = $this->calcularResumenCostoso($documento);
}
return $this->cache[$documento];
}
private function calcularResumenCostoso(Documento $documento): string
{
return "Resumen de: " . $documento->titulo;
}
}
Cuando el Documento deja de usarse en el resto de la aplicación, su entrada en la caché desaparece sin necesidad de un mecanismo manual de limpieza.
Sistemas de eventos y observadores. En un patrón observer, donde varios objetos se suscriben a los eventos de otro, un WeakMap evita que la lista de suscriptores mantenga con vida a observadores que ya nadie más referencia.
class Emisor
{
private WeakMap $listeners;
public function __construct()
{
$this->listeners = new WeakMap();
}
public function suscribir(object $listener, callable $callback): void
{
$this->listeners[$listener] = $callback;
}
public function emitir(string $evento): void
{
foreach ($this->listeners as $listener => $callback) {
$callback($evento);
}
}
}
Metadatos añadidos desde fuera de una clase. Al trabajar con clases de una librería externa, a veces se necesita asociarles información adicional —si un objeto ya fue validado, si está sincronizado, en qué estado se encuentra— sin tocar su definición ni recurrir a la herencia.
$estadosSincronizacion = new WeakMap();
function marcarComoSincronizado(object $entidad, WeakMap $estados): void
{
$estados[$entidad] = true;
}
El recolector de basura en acción
El comportamiento se comprueba con un simple __destruct():
class Sensor
{
public function __destruct()
{
echo "Sensor destruido\n";
}
}
$mapa = new WeakMap();
$sensor = new Sensor();
$mapa[$sensor] = 'lectura: 24°C';
echo "Antes de unset: " . count($mapa) . "\n"; // 1
unset($sensor);
// aquí se imprime "Sensor destruido"
echo "Después de unset: " . count($mapa) . "\n"; // 0
El objeto se destruye en cuanto pierde su última referencia fuerte, sin que haga falta esperar a que el WeakMap sea destruido ni realizar ninguna limpieza manual.
Algunas limitaciones
La referencia débil se aplica solo a las claves, no a los valores: si se guarda un objeto como valor, esa sigue siendo una referencia fuerte normal.
Tampoco admite claves escalares, únicamente objetos. Y no está pensado como reemplazo general de los arrays, sino para el caso específico en el que el ciclo de vida del objeto usado como clave debe mantenerse independiente de la estructura que lo referencia.
Como el mecanismo depende del recolector de basura, en presencia de referencias circulares complejas el momento exacto en que una entrada desaparece puede no ser instantáneo, aunque en la práctica suele resolverse de forma predecible.
Cuándo tiene sentido usarlo
Tiene sentido cuando se necesita asociar datos a un objeto sin alargar su ciclo de vida, al construir cachés o sistemas de eventos, o al añadir metadatos a objetos que no se controlan directamente. También resulta útil en aplicaciones de larga duración —workers, colas, procesos daemon— donde una fuga de memoria pequeña termina notándose con el tiempo.
No suele hacer falta cuando se trabaja con un número reducido de objetos de vida corta, como ocurre en una request HTTP típica, donde el propio final del script libera toda la memoria. Tampoco es la herramienta indicada si lo que se necesita es que los valores también tengan referencias débiles, para lo que existe una clase distinta, WeakReference. Y si un array asociativo con identificadores simples ya resuelve el problema, añadir un WeakMap solo suma complejidad innecesaria.