> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Diseño del ciclo de vida de los servicios de paquetes

> Analiza la implementación interna de bind, singleton y scoped en Laravel 13 y explica cómo diseñar paquetes que no arrastren estado en workers de larga duración y cómo probar los límites del ciclo de vida.

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](/es/service-container) 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

| Registro | Obtención con `make()` normal | Diseño adecuado |
| - | - | - |
| `bind()` | Se crea en cada resolución | Servicios que pueden crearse por operación |
| `singleton()` | Reutiliza la instancia ya creada dentro del mismo contenedor | Servicios que no retienen estado propio de una petición o un job |
| `scoped()` | Reutiliza dentro del mismo ciclo de vida y elimina la instancia guardada en el límite | Estado compartido dentro de una petición o un job |

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.

```mermaid theme={null}
flowchart TD
    A["Registrar scoped en register()"] --> B["Registrar cómo crear y la clave que se eliminará"]
    B --> C["Primer make() en el proceso A"]
    C --> D["Crear y guardar la instancia A"]
    D --> E["Dentro del proceso A se obtiene la misma instancia"]
    E --> F["forgetScopedInstances()<br>en el límite del ciclo de vida"]
    F --> G["Eliminar la instancia guardada en el contenedor"]
    G --> H["make() del proceso B crea la instancia B"]
```

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.

<Warning>
  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.
</Warning>

## 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.

```php theme={null}
<?php

namespace Acme\Courier;

final class DeliveryContext
{
    private ?string $destinationId = null;

    public function setDestinationId(string $destinationId): void
    {
        $this->destinationId = $destinationId;
    }

    public function destinationId(): ?string
    {
        return $this->destinationId;
    }
}
```

En el service provider del paquete se registra un context vacío, sin leer el estado de la aplicación al crearlo.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->scoped(DeliveryContext::class);
    }
}
```

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.

```php theme={null}
$context = app(\Acme\Courier\DeliveryContext::class);
$context->setDestinationId($authorizedDestinationId);
```

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.

<Info>
  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.
</Info>

## 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.

```php theme={null}
$this->app->singleton(DeliveryService::class, function ($app) {
    return new DeliveryService($app->make(DeliveryContext::class));
});
```

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.

```php theme={null}
use Illuminate\Container\Container;

$this->app->singleton(DeliveryService::class, function () {
    return new DeliveryService(
        fn () => Container::getInstance()->make(DeliveryContext::class),
    );
});
```

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.

```php theme={null}
<?php

namespace Acme\Courier\Tests;

use Acme\Courier\DeliveryContext;
use Illuminate\Container\Container;
use PHPUnit\Framework\TestCase;

class ServiceLifetimeTest extends TestCase
{
    public function test_context_is_shared_then_recreated(): void
    {
        $container = new Container;
        $container->scoped(DeliveryContext::class);

        $first = $container->make(DeliveryContext::class);
        $first->setDestinationId('destination-a');

        $this->assertSame($first, $container->make(DeliveryContext::class));
        $this->assertSame('destination-a', $first->destinationId());

        $container->forgetScopedInstances();

        $second = $container->make(DeliveryContext::class);
        $this->assertNotSame($first, $second);
        $this->assertNull($second->destinationId());
    }

    public function test_singleton_keeps_a_captured_scoped_dependency(): void
    {
        $container = new Container;
        $container->scoped(DeliveryContext::class);
        $container->singleton('courier.holder', function ($app) {
            return (object) ['context' => $app->make(DeliveryContext::class)];
        });

        $holder = $container->make('courier.holder');
        $oldContext = $holder->context;

        $container->forgetScopedInstances();

        $this->assertSame($holder, $container->make('courier.holder'));
        $this->assertSame($oldContext, $holder->context);
        $this->assertNotSame($oldContext, $container->make(DeliveryContext::class));
    }
}
```

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.

| Comprobación al actualizar | Resultado esperado |
| - | - |
| Resolver el context varias veces dentro del mismo proceso | Se comparten la misma instancia y el valor asignado |
| Resolver cruzando el límite en el mismo contenedor | Es una instancia nueva y no conserva el valor anterior |
| Un singleton usa el context | No guarda el context antiguo y usa el valor actual |
| Ejecutar peticiones consecutivas de Octane en la aplicación que lo usa | La segunda no hereda el destino de entrega u otros datos de la primera |
| Ejecutar jobs consecutivos en un worker de colas residente | Los valores asignados se aíslan por job |
| Actualizar Laravel u Octane | Volver a revisar el camino de llamada del restablecimiento y la configuración de la aplicación que lo usa |

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

<Columns cols={2}>
  <Card title="Hooks de resolución de servicios en paquetes" icon="puzzle-piece" href="/es/advanced/package-service-resolution">
    Revisa afterResolving, la obtención de instancias compartidas y las diferencias de configuración al volver a resolver.
  </Card>

  <Card title="Laravel Octane" icon="bolt" href="/es/octane">
    Revisa la retención del Request y del contenedor, y las precauciones operativas de los procesos residentes.
  </Card>
</Columns>

## Fuentes primarias consultadas

* [Documentación oficial de Laravel: Binding scoped singletons](https://github.com/laravel/docs/blob/13.x/container.md#binding-scoped-singletons)
* [Documentación oficial de Laravel: Dependency injection and Octane](https://github.com/laravel/docs/blob/13.x/octane.md#dependency-injection-and-octane)
* [Container: scoped(), singleton(), forgetScopedInstances() y resolve()](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Container/Container.php)
* [ContainerTest: compartición y recreación de scoped](https://github.com/laravel/framework/blob/v13.36.0/tests/Container/ContainerTest.php)
* [QueueServiceProvider: resetScope que se pasa al Worker](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Queue/QueueServiceProvider.php)
* [Worker: restablecimiento del scope en daemon()](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Queue/Worker.php)
* [Octane: FlushTemporaryContainerInstances](https://github.com/laravel/octane/blob/ecc9254e6d79cd607f191015f634f9be944e2267/src/Listeners/FlushTemporaryContainerInstances.php)
* [Octane: listeners predeterminados de OperationTerminated](https://github.com/laravel/octane/blob/ecc9254e6d79cd607f191015f634f9be944e2267/config/octane.php)


## Related topics

- [Desarrollo de paquetes para Laravel](/es/advanced/package-development.md)
- [Temas avanzados](/es/advanced/index.md)
- [Eventos del ciclo de vida de sesión](/es/packages/laravel-copilot-sdk/session-lifecycle-event.md)
- [⚡Introducción a Livewire 4 — Crear UIs reactivas sin JavaScript](/es/blog/livewire-introduction.md)
- [Guía de desarrollo de apps con integración de la Engine API - VOICEVOX for Laravel](/es/packages/laravel-voicevox/app-guide.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.