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á
ElContainer::scoped() revisado hace dos cosas.
- Registra la clave en
scopedInstances. - Llama a
singleton()y la registra como binding compartido.
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:
QueueServiceProviderpasa 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 ejecutaforgetScopedInstances()y se limpian las instancias resueltas de las Facades, entre otras cosas. - Octane: la configuración predeterminada oficial registra
FlushTemporaryContainerInstancesenOperationTerminated. Este listener llama aforgetScopedInstances(). - 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.
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.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.
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.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.
- Pasar como argumento del método los valores necesarios en cada proceso. Si no hace falta compartir estado, es lo más sencillo.
- Hacer scoped también al que retiene la referencia. Se recrean tanto
DeliveryServicecomo el context en cada proceso. - 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.
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
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 usaforgetScopedInstances(), igual que las pruebas oficiales de Container, y comprueba el efecto del límite sin arrancar todo el Worker.
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
- Documentación oficial de Laravel: Binding scoped singletons
- Documentación oficial de Laravel: Dependency injection and Octane
- Container: scoped(), singleton(), forgetScopedInstances() y resolve()
- ContainerTest: compartición y recreación de scoped
- QueueServiceProvider: resetScope que se pasa al Worker
- Worker: restablecimiento del scope en daemon()
- Octane: FlushTemporaryContainerInstances
- Octane: listeners predeterminados de OperationTerminated