En PHP, mantener una referencia a un objeto en una variable impide que el recolector de basura lo libere. En aplicaciones de larga duración (workers, daemons, aplicaciones con Swoole/ReactPHP) esto puede acumularse y provocar fugas de memoria. WeakReference y WeakMap permiten apuntar a objetos sin impedir su liberación por el GC.
WeakReference: la referencia que no retiene
WeakReference envuelve un objeto de forma que si el único puntero que queda a ese objeto es una WeakReference, el GC puede liberarlo. Llama a get() para obtener el objeto; devuelve null si ya fue recolectado.
<?php
class Conexion {
public function __construct(public string $host) {}
public function __destruct() {
echo "Conexión a {$this->host} cerradan";
}
}
$conexion = new Conexion('db.ejemplo.com');
$ref = WeakReference::create($conexion);
var_dump($ref->get()); // object(Conexion)
// Liberamos el único puntero fuerte al objeto
unset($conexion);
// El GC puede haberlo liberado; get() devuelve null
var_dump($ref->get()); // NULL
// Imprime: "Conexión a db.ejemplo.com cerrada"
WeakMap (PHP 8.0+): caché de objetos sin memory leak
WeakMap es un mapa con objetos como claves. Cuando un objeto deja de tener referencias fuertes, su entrada en el WeakMap desaparece automáticamente. Es perfecto para asociar datos adicionales a objetos sin riesgo de que los objetos queden retenidos en memoria más tiempo del necesario.
<?php
class Peticion {
public function __construct(public string $uri) {}
}
$cache = new WeakMap();
function procesarPeticion(Peticion $pet, WeakMap $cache): array {
if (isset($cache[$pet])) {
echo "Desde cachén";
return $cache[$pet];
}
$resultado = ['uri' => $pet->uri, 'ts' => time()];
$cache[$pet] = $resultado;
return $resultado;
}
$p1 = new Peticion('/api/usuarios');
procesarPeticion($p1, $cache); // Calcula
procesarPeticion($p1, $cache); // Desde caché
echo count($cache); // 1
unset($p1); // El objeto ya no tiene referencias fuertes
echo count($cache); // 0 la entrada desaparece sola
Caso práctico: listener de eventos sin fugas
Un event dispatcher que guarda listeners en un array fuerte retiene los objetos listener aunque el resto del código ya no los use. Con WeakMap, los listeners se limpian solos cuando nadie más los referencia:
<?php
class EventDispatcher {
// Clave: objeto listener; valor: array de eventos que escucha
private WeakMap $listeners;
public function __construct() {
$this->listeners = new WeakMap();
}
public function on(object $listener, string $evento): void {
if (!isset($this->listeners[$listener])) {
$this->listeners[$listener] = [];
}
$this->listeners[$listener][] = $evento;
}
public function dispatch(string $evento): void {
foreach ($this->listeners as $listener => $eventos) {
if (in_array($evento, $eventos)) {
$listener->handle($evento);
}
}
}
public function contarListeners(): int {
return count($this->listeners);
}
}
class MiListener {
public function handle(string $e): void { echo "Evento: $en"; }
}
$dispatcher = new EventDispatcher();
$l1 = new MiListener();
$dispatcher->on($l1, 'usuario.creado');
echo $dispatcher->contarListeners(); // 1
unset($l1);
echo $dispatcher->contarListeners(); // 0 se limpió solo
WeakMap vs SplObjectStorage
SplObjectStorage también usa objetos como claves, pero mantiene referencias fuertes. Úsala cuando quieres controlar explícitamente el ciclo de vida de los objetos; usa WeakMap cuando quieres que los objetos vivan y mueran según su propio ciclo de vida sin interferencias del mapa.
<?php
$spl = new SplObjectStorage();
$weak = new WeakMap();
$obj = new stdClass();
$spl->attach($obj);
$weak[$obj] = true;
unset($obj);
echo count($spl); // 1 SplObjectStorage retiene el objeto
echo count($weak); // 0 WeakMap lo libera automáticamente
Cuándo usar cada uno
- WeakReference: cuando quieres observar si un objeto sigue vivo sin impedir que muera. Útil en registros de debug, monitores de recursos o cuando implementas un patrón Observer sin acoplar el ciclo de vida del observador al observado.
- WeakMap: cuando necesitas asociar metadatos o caché a objetos existentes. La clave es siempre un objeto. Si el objeto muere, el par clave-valor desaparece sin tener que limpiarlo manualmente.
