Skip to main content

Qué es once()

once() es un helper global que ejecuta una callback y guarda su resultado en memoria durante la petición. Si se invoca de nuevo con la misma callback desde el mismo lugar de llamada, devuelve el resultado cacheado.
Cuando se llama desde el método de una instancia de un objeto, la caché es independiente por instancia.
Este mecanismo, en el que la caché es independiente «por lugar de llamada» y «por instancia», es distinto de un simple helper de memoización tipo memoize. Para entenderlo hay que mirar las dos clases Illuminate\Support\Once y Illuminate\Support\Onceable.

La llamada en helpers.php

El cuerpo de la función once() en Illuminate\Support\helpers.php es muy sencillo.
Lo importante es que once() en sí no guarda ninguna caché: monta una instancia de Onceable a partir de la información del llamador obtenida con debug_backtrace() y delega el trabajo en la clase Once.

Onceable — cálculo del hash que identifica el lugar de llamada

La clase Onceable calcula, a partir del backtrace, un hash que identifica de forma única «desde qué lugar de llamada» se ha invocado.
La información base del hash es: ruta del archivo + nombre de clase + nombre de la función + número de línea + valores de las variables capturadas con use por el closure. Es decir, aunque el once() se llame desde la misma línea, si cambian los valores capturados con use, se tratará como una caché distinta.
Si llamas a once() desde código ejecutado con eval(), hashFromTrace() devuelve null y no se hace ninguna caché. Es una medida de seguridad pensada para las vistas Blade compiladas y otros casos que pasan por eval.
object indica «si el llamador es un método de instancia». $trace[1]['object'] es el objeto de un nivel arriba en debug_backtrace() (es decir, el que llamó a once()), por lo que si es un método de instancia contiene $this, y si es un método estático o una función global es null.

Once — la caché en sí basada en WeakMap

El almacenamiento real de la caché lo asume Illuminate\Support\Once.
La tecnología clave es WeakMap. Se usa $onceable->object (la instancia del llamador) como clave y, como valor, un array asociativo hash → resultado. Cuando no hay object (función global o método estático), la clave pasa a ser el propio $this de Once, lo que en la práctica implica una única zona de caché compartida en todo el proceso.
Como se utiliza WeakMap, en cuanto no queden referencias al objeto usado como clave, la caché asociada a ese objeto también será candidata al recolector de basura. Es un diseño que dificulta las fugas de memoria aunque once() se llame desde muchos objetos.

Uso en pruebas — enable / disable / flush

Con Once::disable() la caché se desactiva por completo y once() ejecuta la callback en cada llamada. Es útil cuando en las pruebas quieres «un valor nuevo cada vez».
Once::flush() descarta la caché entera y en el próximo acceso vuelve a crear un WeakMap nuevo. Se usa en pruebas de comandos Artisan o en entornos como Octane donde los procesos se reutilizan y no quieres arrastrar la caché entre peticiones.

PreventsCircularRecursion — una aplicación de Onceable

Onceable::tryFromTrace() no es exclusivo de once(): también lo reutiliza el trait Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion de Eloquent. Este trait sirve para «impedir que el mismo lugar de llamada del mismo objeto vuelva a entrar dentro de la pila de llamadas».
La diferencia decisiva con Once es que la caché se limpia una vez terminada la llamada, en el bloque finally. Mientras que Once mantiene la caché de forma permanente durante la petición, PreventsCircularRecursion reutiliza el mismo mecanismo de Onceable para cachear (en realidad, «bloquear») únicamente durante la pila de llamadas en curso. Un uso típico en modelos Eloquent es evitar que el propio toArray() o un accessor se refiera recursivamente a sí mismo.

Aplicaciones en el desarrollo de paquetes

Si en tu propio paquete quieres «ejecutar una sola vez por petición la misma llamada al mismo método de la misma instancia», usar directamente once() es lo más sencillo. Rara vez tendrás que usar Onceable/Once directamente, pero entender la implementación interna resulta útil en situaciones como estas:
  • Cuando quieras controlar el comportamiento de la caché en comandos Artisan o pruebas con Once::disable() / Once::flush().
  • Cuando quieras implementar en un trait propio una caché «por lugar de llamada» o una protección contra reentradas (puedes seguir el mismo patrón que PreventsCircularRecursion).
  • Cuando el resultado de once() no sea el esperado y necesites depurar: conocer los elementos que forman parte del hash (archivo, clase, función, línea, variables use) te ayuda a identificar la causa.
Como once() incluye en el hash también las variables capturadas con use por el closure, si dentro de un bucle pasas un closure que captura valores distintos en cada iteración, cada iteración usará una caché distinta y, en la práctica, no se cacheará nada. Cuando lo uses dentro de un bucle, comprueba que la granularidad de la clave de caché es la que esperas.

Páginas relacionadas

Helper tap() y trait Tappable

Patrón de implementación del helper tap() que devuelve el valor a la vez que intercala efectos colaterales.

Observers de Eloquent y eventos del modelo

Gestión centralizada mediante eventos y observers de los modelos Eloquent.
Última modificación el 11 de septiembre de 2026