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

# Progettare il ciclo di vita dei servizi di un pacchetto

> Analisi dell'implementazione interna di bind, singleton e scoped in Laravel 13: come progettare pacchetti che non trascinano stato nei worker di lunga durata e come testare i confini del ciclo di vita.

Per rendere un pacchetto utilizzabile non solo nelle normali richieste HTTP, ma anche con Octane e con i queue worker, devi decidere **per quanto tempo e che cosa può conservare** ciascun servizio. Se salvi dati specifici di una richiesta in un singleton, un'elaborazione diversa potrebbe leggere dati obsoleti.

Questa pagina esamina l'implementazione del container e del queue worker di Laravel Framework `v13.36.0`, oltre a quella di Octane, e collega i metodi di registrazione di base del [service container](/it/service-container) alla manutenzione a lungo termine di un pacchetto. Scegliere `scoped()` non basta a reimpostare tutti i riferimenti tra oggetti e tutto lo stato statico.

## Scegliere il metodo di registrazione in base alla durata del servizio

| Registrazione | Recupero con un normale `make()` | Progettazione adatta |
| - | - | - |
| `bind()` | Crea un'istanza a ogni risoluzione | Servizi che possono essere creati per singola operazione |
| `singleton()` | Riutilizza l'istanza già creata nello stesso container | Servizi che non conservano stato specifico di una richiesta o di un job |
| `scoped()` | Riutilizza l'istanza nello stesso ciclo di vita e rimuove l'istanza salvata al confine | Stato condiviso all'interno di una singola richiesta o di un singolo job |

Questa tabella confronta le risoluzioni normali. I percorsi speciali, come la risoluzione con parametri espliciti o il contextual binding, vanno verificati separatamente.

Anche con `bind()`, se salvi il servizio ottenuto in una proprietà di un altro singleton, quel riferimento rimane. Il metodo di registrazione è una regola di creazione e riutilizzo del container, non una regola che obbliga a ricreare gli oggetti conservati da chi li usa.

## scoped combina una registrazione condivisa con il tracciamento delle istanze da rimuovere

Il metodo `Container::scoped()` esaminato esegue due operazioni.

1. Registra la chiave in `scopedInstances`.
2. Chiama `singleton()` e la registra come binding condiviso.

L'istanza stessa viene salvata nello stesso `instances` usato dai singleton. `forgetScopedInstances()` esegue `unset()` sulle istanze salvate per le chiavi registrate. La logica di creazione del binding rimane, quindi la successiva risoluzione normale può creare un nuovo oggetto.

```mermaid theme={null}
flowchart TD
    A["Registrazione scoped in register()"] --> B["Memorizzazione della creazione e della chiave da rimuovere"]
    B --> C["Primo make() nell'elaborazione A"]
    C --> D["Creazione e salvataggio dell'istanza A"]
    D --> E["Stessa istanza all'interno dell'elaborazione A"]
    E --> F["forgetScopedInstances()<br>al confine del ciclo di vita"]
    F --> G["Rimozione dell'istanza salvata nel container"]
    G --> H["make() nell'elaborazione B crea l'istanza B"]
```

Questa operazione non reimposta le proprietà del vecchio oggetto e non riscrive i riferimenti conservati da altri oggetti. Un vecchio oggetto ancora referenziato può continuare a essere usato.

### Il confine lo crea l'ambiente di esecuzione

Il container non monitora da solo l'inizio delle richieste HTTP o dei job.

* **Queue worker**: `QueueServiceProvider` passa al Worker una callback che reimposta lo scope. `Worker::daemon()` la invoca nel ciclo prima di prelevare il job successivo e, all'interno della callback, esegue `forgetScopedInstances()` e la pulizia delle istanze risolte dalle Facade, tra le altre cose.
* **Octane**: la configurazione predefinita ufficiale registra `FlushTemporaryContainerInstances` su `OperationTerminated`. Questo listener chiama `forgetScopedInstances()`.
* **Comandi residenti o cicli personalizzati**: ripetere semplicemente `make()` non crea un confine. Il ciclo di vita deve essere progettato dall'infrastruttura di esecuzione.

<Warning>
  Non chiamare `forgetScopedInstances()` di tua iniziativa dalla normale logica applicativa o dal `boot()` di un pacchetto. Influisce su tutti i servizi scoped del container. Inoltre, non tutti i percorsi di esecuzione dei job, compresa l'esecuzione sincrona, passano necessariamente per lo stesso percorso di reset del Worker residente.
</Warning>

## Rendere scoped lo stato condiviso all'interno di un'elaborazione

Considera un pacchetto di spedizioni che vuole condividere l'identificativo della destinazione all'interno di una singola richiesta o di un singolo job. Invece di conservare direttamente la Request HTTP, salva solo il valore necessario.

```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;
    }
}
```

Nel service provider del pacchetto, registra un context vuoto senza leggere lo stato dell'utente al momento della creazione.

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

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

Nel controller o nel `handle()` del job dell'applicazione che usa il pacchetto, imposta un identificativo per cui sono già stati completati la validazione dell'input e il controllo dei permessi. Gli altri servizi che risolvono questo tipo nello stesso ciclo di vita possono fare riferimento allo stesso context.

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

Questo context non è un meccanismo di autorizzazione. Poter impostare un identificativo è diverso dall'essere autorizzati ad accedere a quella destinazione. Inoltre, `scoped()` non è una forma di persistenza su database o cache esterna. I valori da passare al job successivo vanno passati esplicitamente come dati del job.

<Info>
  Con Octane, `register()` e `boot()` dei service provider vengono eseguiti all'avvio iniziale del worker. Non progettare il codice in modo da salvare nel context l'utente o la destinazione correnti in `boot()`. I valori specifici di ogni elaborazione vanno impostati dopo l'inizio dell'elaborazione.
</Info>

## I riferimenti da un singleton a un servizio scoped non vengono aggiornati automaticamente

Con la registrazione seguente, rendere scoped solo il context non produce il comportamento atteso.

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

Se `DeliveryService` salva il context in una proprietà nel costruttore, continua a conservare il primo context creato. Anche se il container rimuove il context salvato, la proprietà del `DeliveryService` già creato non cambia.

Scegli la soluzione in base alla responsabilità del servizio.

1. **Passare i valori necessari a ogni elaborazione come argomenti dei metodi**. Se non serve condividere stato, è la soluzione più semplice.
2. **Rendere scoped anche il servizio che conserva il riferimento**. Sia `DeliveryService` sia il context vengono ricreati per ogni elaborazione.
3. **Passare un resolver che risolve al momento dell'uso**. Se devi mantenere il singleton, recupera ogni volta il servizio necessario dal container corrente.

Nel terzo esempio, invece di catturare il vecchio context in una closure, lo si risolve al momento della chiamata.

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

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

In questo esempio `DeliveryService` deve ricevere una `Closure` nel costruttore ed essere modificato in un'**API che la invoca a ogni operazione**. Invocare il resolver una sola volta nel costruttore e salvarne il risultato non risolve il problema. Si tratta di un esempio di progettazione che applica il container resolver della documentazione ufficiale di Octane a un context specifico del pacchetto.

### Verificare anche lo stato che scoped non rimuove

`scoped()` non rimuove automaticamente lo stato seguente.

* Valori aggiunti a proprietà statiche proprie del pacchetto
* Riferimenti al context salvati in un altro singleton o in variabili globali
* "Utente corrente" o "destinazione corrente" conservati all'interno di un singleton
* Riferimenti conservati da Facade o livelli di cache personalizzati

Distingui il reset effettuato dall'infrastruttura di esecuzione standard dal reset dello stato proprio del pacchetto. Ridurre i punti in cui si conserva lo stato specifico di ogni elaborazione è più semplice che mantenere una logica di pulizia personalizzata per ogni ambiente di esecuzione.

## Scrivere test che attraversano un confine nello stesso container

Se i test HTTP si limitano a creare ogni volta una nuova applicazione, possono sfuggire i problemi dovuti al riutilizzo dello stesso container in un processo residente. Per prima cosa, verifica con il solo container la condivisione all'interno della stessa elaborazione e la ricreazione dopo il confine.

I test seguenti sono in formato PHPUnit. Il primo test, come i test ufficiali del Container, usa `forgetScopedInstances()` per verificare l'effetto del confine senza avviare l'intero 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));
    }
}
```

Il secondo test documenta il comportamento per cui il riferimento rimane. Nel pacchetto reale aggiungi anche test che verificano che la soluzione adottata usi un nuovo context o un nuovo valore.

| Verifica durante gli aggiornamenti | Risultato atteso |
| - | - |
| Risolvere il context più volte nella stessa elaborazione | Condivide la stessa istanza e il valore impostato |
| Risolvere attraversando un confine nello stesso container | Nuova istanza, senza il valore precedente |
| Un singleton usa il context | Non salva il vecchio context e usa il valore corrente |
| Eseguire richieste Octane consecutive nell'applicazione | La seconda non eredita la destinazione o altri dati della prima |
| Eseguire job consecutivi in un queue worker residente | I valori impostati sono isolati per ogni job |
| Aggiornare Laravel o Octane | Riverificare il percorso di chiamata del reset e la configurazione dell'applicazione |

I test del solo container non verificano la sandbox di Octane né il percorso di esecuzione reale del queue worker. Servono anche test di integrazione che eseguano elaborazioni consecutive negli ambienti di esecuzione che dichiari di supportare. Nei deploy che modificano il metodo di registrazione o l'implementazione dei servizi, ricarica i processi residenti per applicare il nuovo codice.

## Pagine correlate

<Columns cols={2}>
  <Card title="Hook di risoluzione dei servizi nei pacchetti" icon="puzzle-piece" href="/it/advanced/package-service-resolution">
    Approfondisce afterResolving, il recupero delle istanze condivise e le differenze di configurazione alla nuova risoluzione.
  </Card>

  <Card title="Laravel Octane" icon="bolt" href="/it/octane">
    Approfondisce la conservazione della Request e del container e le avvertenze operative sui processi residenti.
  </Card>
</Columns>

## Fonti primarie consultate

* [Documentazione ufficiale di Laravel: Binding scoped singletons](https://github.com/laravel/docs/blob/13.x/container.md#binding-scoped-singletons)
* [Documentazione ufficiale di Laravel: Dependency injection and Octane](https://github.com/laravel/docs/blob/13.x/octane.md#dependency-injection-and-octane)
* [Container: scoped(), singleton(), forgetScopedInstances(), resolve()](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Container/Container.php)
* [ContainerTest: condivisione e ricreazione con scoped](https://github.com/laravel/framework/blob/v13.36.0/tests/Container/ContainerTest.php)
* [QueueServiceProvider: resetScope passato al Worker](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Queue/QueueServiceProvider.php)
* [Worker: reset dello scope in 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: listener predefiniti di OperationTerminated](https://github.com/laravel/octane/blob/ecc9254e6d79cd607f191015f634f9be944e2267/config/octane.php)


## Related topics

- [Sviluppo di pacchetti Laravel](/it/advanced/package-development.md)
- [Argomenti avanzati](/it/advanced/index.md)
- [Sovrascrivere e aggiornare le view di un pacchetto](/it/advanced/package-views.md)
- [Ciclo di vita della richiesta](/it/lifecycle.md)
- [Hook di risoluzione dei servizi nei pacchetti](/it/advanced/package-service-resolution.md)


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