Skip to main content
Para que un paquete funcione no solo en peticiones HTTP normales, sino también con Octane y con workers de colas, hay que decidir durante cuánto tiempo y qué puede retener cada servicio. Si guardas datos propios de una petición en un singleton, otro proceso podría acabar consultando datos antiguos. En esta página se revisa la implementación del contenedor, del worker de colas y de Octane en Laravel Framework v13.36.0, y se relacionan los métodos básicos de registro del contenedor de servicios con el mantenimiento a largo plazo de un paquete. Elegir scoped() no basta para que se restablezcan todas las referencias entre objetos ni todo el estado estático.

Elegir el método de registro según el tiempo de retención del servicio

Esta tabla compara la resolución normal. Revisa por separado los caminos especiales, como la resolución con parámetros explícitos o el contextual binding. Incluso con bind(), si guardas el servicio obtenido en una propiedad de otro singleton, esa referencia permanece. El método de registro es una regla de creación y reutilización del contenedor, no una regla que obligue a recrear los objetos que retiene quien los usa.

scoped combina un registro compartido con el seguimiento de lo que se eliminará

El Container::scoped() revisado hace dos cosas.
  1. Registra la clave en scopedInstances.
  2. Llama a singleton() y la registra como binding compartido.
La instancia en sí se guarda en el mismo instances que un singleton. forgetScopedInstances() ejecuta unset() sobre las instancias guardadas de las claves registradas. El proceso de creación del binding se conserva, así que la siguiente resolución normal puede crear un objeto nuevo. Este proceso no inicializa las propiedades del objeto antiguo ni reescribe las referencias que tengan otros objetos. Un objeto antiguo al que aún se haga referencia puede seguir usándose.

Quien crea el límite es el entorno de ejecución

El contenedor en sí no vigila el inicio de las peticiones HTTP ni de los jobs.
  • Worker de colas: QueueServiceProvider pasa al Worker un callback que restablece el scope. Worker::daemon() lo llama en el bucle antes de obtener el siguiente job, y dentro del callback se ejecuta forgetScopedInstances() y se limpian las instancias resueltas de las Facades, entre otras cosas.
  • Octane: la configuración predeterminada oficial registra FlushTemporaryContainerInstances en OperationTerminated. Este listener llama a forgetScopedInstances().
  • Comandos residentes o bucles propios: repetir make() sin más no crea un límite. Hay que diseñar el ciclo de vida desde la infraestructura de ejecución.
No llames a forgetScopedInstances() por tu cuenta desde el código normal de la aplicación ni desde el boot() de un paquete. Afecta a todos los servicios scoped del contenedor. Además, no todos los caminos de ejecución de jobs, incluida la ejecución síncrona, pasan necesariamente por el mismo camino de restablecimiento que un Worker residente.

Hacer scoped el estado compartido dentro de un proceso

Piensa en un paquete de envíos que quiere compartir el identificador del destino de entrega dentro de una petición o un job. No retiene directamente el Request de HTTP, sino que guarda solo el valor necesario.
En el service provider del paquete se registra un context vacío, sin leer el estado de la aplicación al crearlo.
En el controlador o en el handle() del job de la aplicación que lo usa, se asigna el identificador después de validar la entrada y comprobar los permisos. Otros servicios que resuelvan este tipo dentro del mismo ciclo de vida verán el mismo context.
Este context no es un mecanismo de autorización. Poder asignar un identificador y tener permiso para acceder a ese destino de entrega son cosas distintas. Además, scoped() tampoco persiste nada en la base de datos ni en una caché externa. Los valores que deban llegar al siguiente job se pasan explícitamente como datos del job.
En Octane, register() y boot() de los service providers se ejecutan al arrancar inicialmente el worker. No diseñes el context para guardar el usuario actual o el destino de entrega en boot(). Los valores de cada proceso se asignan una vez que el proceso ha empezado.

Las referencias de un singleton a un servicio scoped no se actualizan automáticamente

El siguiente registro no funciona como se espera aunque solo el context sea scoped.
Si DeliveryService guarda el context en una propiedad desde el constructor, conservará el primer context creado. Aunque el contenedor elimine el context guardado, la propiedad del DeliveryService ya creado no cambia. Elige la solución según la responsabilidad del servicio.
  1. Pasar como argumento del método los valores necesarios en cada proceso. Si no hace falta compartir estado, es lo más sencillo.
  2. Hacer scoped también al que retiene la referencia. Se recrean tanto DeliveryService como el context en cada proceso.
  3. Pasar un resolver que resuelva en el momento de uso. Si necesitas mantener el singleton, obtén cada vez el servicio necesario del contenedor actual.
En el tercer ejemplo, en lugar de capturar el context antiguo en la closure, se resuelve en el momento de la llamada.
El DeliveryService de este ejemplo debe cambiarse para que reciba un Closure en el constructor y tenga una API que lo llame en cada operación. Llamar al resolver una sola vez en el constructor y guardar el resultado no resuelve el problema. Es un ejemplo de diseño que aplica el container resolver de la documentación oficial de Octane a un context propio del paquete.

Revisar también el estado que scoped no elimina

scoped() no elimina automáticamente el siguiente estado.
  • Valores añadidos a propiedades estáticas propias del paquete
  • Referencias al context guardadas en otro singleton o en variables globales
  • El «usuario actual» o el «destino de entrega actual» retenidos dentro de un singleton
  • Referencias retenidas por Facades o capas de caché propias
Distingue entre el restablecimiento que hace la infraestructura de ejecución estándar y el restablecimiento del estado propio del paquete. Reducir los lugares donde se retiene estado por proceso es más sencillo que mantener procesos de limpieza propios en todos los entornos de ejecución.

Escribir pruebas que crucen el límite en el mismo contenedor

Si las pruebas HTTP solo crean una aplicación nueva cada vez, puedes pasar por alto los problemas de un proceso residente que sigue usando el mismo contenedor. Primero, verifica con el contenedor aislado que se comparte dentro del mismo proceso y que se recrea tras el límite. Las siguientes pruebas usan el formato de PHPUnit. La primera usa forgetScopedInstances(), igual que las pruebas oficiales de Container, y comprueba el efecto del límite sin arrancar todo el Worker.
La segunda prueba documenta el comportamiento por el que la referencia permanece. En un paquete real, añade también pruebas que verifiquen que, con la solución adoptada, se usan el nuevo context o los nuevos valores. Las pruebas del contenedor aislado no verifican el sandbox de Octane ni el camino de ejecución real del worker de colas. También necesitas pruebas de integración que ejecuten procesos consecutivos en los entornos de ejecución que declares compatibles. En los despliegues que cambien el método de registro o la implementación de los servicios, recarga los procesos residentes para aplicar el código nuevo.

Páginas relacionadas

Hooks de resolución de servicios en paquetes

Revisa afterResolving, la obtención de instancias compartidas y las diferencias de configuración al volver a resolver.

Laravel Octane

Revisa la retención del Request y del contenedor, y las precauciones operativas de los procesos residentes.

Fuentes primarias consultadas

Última modificación el 14 de octubre de 2026