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

# Job in coda dei pacchetti e compatibilità durante gli aggiornamenti

> Analizza come Laravel 13 salva e ripristina i job e spiega la compatibilità di nomi di classe e proprietà, il nuovo recupero dei modelli, i test dei vecchi payload e l'ordine di aggiornamento dei worker.

Fai in modo che i job accodati prima di un aggiornamento del pacchetto possano essere elaborati anche dopo. Ciò che la coda salva non è il codice da eseguire, ma il nome della classe del job e il suo stato. Oltre ai test unitari del nuovo codice, servono **test che ripristinano con il nuovo codice lo stato salvato dal vecchio codice**.

> [!NOTE]
>
> Questa pagina riguarda Laravel 13 e verifica l'implementazione di Framework `v13.42.0`. Presuppone le [basi delle code](https://kawax.biz/it/queues.md) e i [test dei pacchetti](https://kawax.biz/it/advanced/package-testing.md). La progettazione della compatibilità descritta di seguito è una politica del pacchetto. Laravel non offre una funzione che migri automaticamente i tuoi formati di payload.

## Il codice cambia tra salvataggio ed esecuzione

Per i job oggetto, `Queue::createObjectPayload()` esegue `serialize(clone $job)` e inserisce il risultato in `data.command` del payload. Con la cifratura attiva, la stessa serializzazione avviene prima.

Lato worker, `CallQueuedHandler::getCommand()` applica `unserialize()` ai dati salvati e passa il job ripristinato al Dispatcher. Un normale `handle()` viene eseguito con il codice e il service container del worker in quel momento.

```mermaid theme={null}
sequenceDiagram
    participant P as Lato che accoda<br>vecchia versione del pacchetto
    participant Q as Coda
    participant W as Worker<br>nuova versione del pacchetto

    P->>Q: Salva nome della classe e proprietà
    Note over P,W: Il deploy aggiorna il codice
    Q->>W: Recupera il payload salvato
    W->>W: Ripristina nella classe attuale
    W->>W: Esegue l'handle() attuale
```

Se non riavvii i worker di lunga durata, il vecchio codice continua a girare dopo l'aggiornamento. Con i rolling deploy, considera non solo "vecchio lato che accoda con nuovo worker", ma anche "nuovo lato che accoda con vecchio worker".

| Cosa cambi | Effetto sui job salvati |
| - | - |
| Nome di classe completo del job | Se la classe non si può caricare con il vecchio nome, il job non viene ripristinato correttamente |
| Nome e visibilità delle proprietà | Le chiavi al salvataggio e al ripristino potrebbero non coincidere più |
| Tipo delle proprietà | I vecchi valori potrebbero non essere assegnabili e il ripristino potrebbe fallire |
| Valore predefinito dichiarato delle proprietà | Il significato dei valori il cui salvataggio è stato omesso potrebbe cambiare |
| `handle()` o i servizi da cui dipende | Anche se il ripristino riesce, il risultato dell'elaborazione dei vecchi dati potrebbe cambiare |

Non valutare la ridenominazione o la rimozione di una classe solo in base al fatto che gli utenti la chiamino direttamente. Anche i job in attesa, ritardati o in attesa di nuovo tentativo, e il periodo di conservazione dei job falliti, fanno parte della compatibilità.

## Cosa salva SerializesModels

`Illuminate\Foundation\Queue\Queueable` usa internamente `SerializesModels`. Oltre a sostituire i modelli, fornisce `__serialize()`, che elenca le proprietà del job stesso, e `__unserialize()`, che reimposta i valori.

### I valori uguali al valore predefinito dichiarato non vengono salvati

`__serialize()` esclude le proprietà statiche e non inizializzate, e **omette il salvataggio anche quando il valore attuale è strettamente uguale al valore predefinito dichiarato della proprietà**.

```php theme={null}
public string $format = 'csv';
```

Se questa proprietà resta `'csv'`, l'array salvato non contiene `format`. Se una release successiva cambia il valore predefinito dichiarato in `'json'`, `'json'` può applicarsi anche ai vecchi job. Non è una modifica estetica del valore predefinito, ma un cambiamento nel modo in cui si leggono i vecchi dati.

### Il costruttore non viene richiamato durante il ripristino

`__unserialize()` assegna i valori alle proprietà che esistono nella classe attuale e che hanno una chiave nell'array salvato. Non esegue alcun completamento o validazione dell'input tramite il costruttore.

```php theme={null}
public function __construct(
    public string $format = 'csv',
) {}
```

Il `'csv'` di questo esempio è **il valore predefinito di un argomento del costruttore**. Non è il valore predefinito dichiarato della proprietà stessa, quindi non completa il valore quando ripristini un vecchio payload senza `format`. Se una proprietà tipizzata aggiunta resta non inizializzata, un accesso successivo fallisce.

Inoltre, la chiave di una proprietà public è il suo nome, quella di una protected ha un prefisso di visibilità e quella di una private include anche il nome della classe. Verifica con i vecchi payload anche un passaggio da public a private.

> [!WARNING]
>
> `SerializesModels` elenca le proprietà del job. Non dare per scontato che la stessa conversione avvenga ricorsivamente all'interno di DTO arbitrari o sui modelli annidati negli array. Se passi oggetti diversi dai modelli, anche le loro classi e i loro formati di salvataggio fanno parte della compatibilità.

## Separare identificatori e servizi di esecuzione

Salva nel job gli identificatori e le opzioni necessari all'esecuzione, e inietta in `handle()` i servizi di esecuzione come client API o exporter. Se passi un servizio al costruttore e lo conservi, anche il suo stato interno potrebbe essere salvato.

L'esempio seguente aggiunge la scelta del formato e una versione del payload a un job con lo stesso nome che nella vecchia versione aveva solo `reportId`. `ReportExporter` è un contratto che il pacchetto fornisce e registra nel container, con `export(string $reportId, string $format): void`.

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

namespace Acme\Reports\Jobs;

use Acme\Reports\Contracts\ReportExporter;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use UnexpectedValueException;

class GenerateReport implements ShouldQueue
{
    use Queueable;

    public string $format = 'csv';

    public int $payloadVersion = 1;

    public function __construct(
        public string $reportId,
        string $format = 'csv',
    ) {
        $this->format = $format;
        $this->payloadVersion = 2;
    }

    public function handle(ReportExporter $exporter): void
    {
        if (! in_array($this->payloadVersion, [1, 2], true)) {
            throw new UnexpectedValueException('Unsupported report payload version.');
        }

        $exporter->export($this->reportId, $this->format);
    }
}
```

Questo design separa il significato dei valori mancanti dai valori accodati di nuovo.

* Se un vecchio payload non ha `format`, si usa il valore predefinito dichiarato `'csv'`.
* Se un vecchio payload non ha `payloadVersion`, viene trattato come il valore predefinito dichiarato `1`.
* Per i nuovi job, il costruttore imposta `payloadVersion` a `2`. Poiché differisce dal valore predefinito dichiarato, questo valore viene salvato.
* Se il `format` di un nuovo job è `'json'`, anche quel valore viene salvato. `'csv'` viene omesso, quindi mantieni anche in futuro il valore predefinito dichiarato `'csv'`.

Se in futuro vuoi cambiare solo il formato predefinito dei nuovi job, puoi modificare il valore predefinito dell'argomento del costruttore e l'assegnazione, valutandoli separatamente dal valore predefinito della proprietà usato per i vecchi dati. Gestisci la versione del payload non come numero di release del pacchetto, ma come **generazione del formato di salvataggio e della sua interpretazione**.

> [!TIP]
>
> Aggiungere un numero di versione non basta a garantire la compatibilità. La nuova versione di questo esempio sa elaborare anche il vecchio formato, ma nulla garantisce che i vecchi worker comprendano il nuovo `format` o `payloadVersion`. Serve anche l'ordine di aggiornamento del lato che accoda e dei worker descritto più avanti.

## Distinguere il passaggio di modelli dagli snapshot

Se salvi un modello direttamente in una proprietà, `SerializesAndRestoresModelIdentifiers` lo sostituisce con un `ModelIdentifier` contenente classe, ID, nome della connessione, nomi delle relazioni caricate e così via. Nel ripristino di un singolo modello, lo recupera di nuovo dal database con `firstOrFail()` e carica le relazioni interessate.

Non è un meccanismo che fissa gli attributi o i dati correlati al momento dell'accodamento.

* Se il record viene aggiornato prima dell'esecuzione, il modello ripristinato riflette il contenuto aggiornato.
* I vincoli applicati in anticipo alle relazioni non vengono salvati, quindi riapplica le condizioni necessarie al momento dell'esecuzione.
* Escludi dal salvataggio le relazioni non necessarie con `withoutRelations()` o `#[WithoutRelations]`.
* Se il modello è stato eliminato, il job può fallire nella fase di ripristino, prima del suo `handle()`.

Se è accettabile scartare i job il cui modello è stato eliminato, puoi valutare l'attributo ufficiale `#[DeleteWhenMissingModels]`. Tuttavia il pacchetto non dovrebbe rendere lo scarto il comportamento predefinito di sua iniziativa, ma esporre la politica di errore agli utenti. Se passi solo un ID e lo cerchi in `handle()`, sei tu a controllare il fallimento o il salto di quella ricerca.

Se ti serve il contenuto al momento dell'accodamento, salva esplicitamente i valori necessari, oppure registra nel database qualcosa come una richiesta di report immutabile e passa il suo ID. Distingui il semplice passaggio di un modello dal salvataggio di uno snapshot dei dati di business.

### L'accodamento dopo il commit è un problema diverso

Quando elabori record creati all'interno di una transazione, impedisci ai worker di iniziare a leggerli prima del commit.

```php theme={null}
use Acme\Reports\Jobs\GenerateReport;
use Illuminate\Support\Facades\DB;

DB::transaction(function () use ($reportId) {
    // Crea o aggiorna il record di destinazione in questa transazione.

    GenerateReport::dispatch($reportId)->afterCommit();
});
```

`afterCommit()` attende il commit della transazione padre aperta prima di accodare e scarta l'accodamento in caso di rollback. Non risolve la compatibilità di nomi di classe e proprietà né l'esecuzione duplicata di elaborazioni esterne.

## Separare i fake dai test dei vecchi payload

Un normale `Queue::fake()` registra gli accodamenti. Un `assertPushed()` che passa non conferma da solo il salvataggio e il ripristino, né che la nuova versione elabori i vecchi payload.

Puoi verificare un ciclo completo con la classe attuale così.

```php theme={null}
use Acme\Reports\Jobs\GenerateReport;
use Illuminate\Support\Facades\Queue;

Queue::fake()->serializeAndRestore();

GenerateReport::dispatch('report-001', 'json');

Queue::assertPushed(GenerateReport::class, function (GenerateReport $job) {
    return $job->reportId === 'report-001'
        && $job->format === 'json'
        && $job->payloadVersion === 2;
});
```

`serializeAndRestore()` esegue `serialize()` e `unserialize()` all'interno del fake. Tuttavia non verifica l'accodamento su un backend reale, l'esecuzione da parte di un worker né la generazione con il vecchio codice.

### Conservare le fixture generate con la vecchia versione

Finché il vecchio `GenerateReport` ha solo `reportId`, genera un job fisso che non contiene dati reali. Il codice di preparazione seguente va eseguito una volta nell'ambiente di test della vecchia versione. Crea prima `tests/fixtures/`.

```php theme={null}
use Acme\Reports\Jobs\GenerateReport;

file_put_contents(
    __DIR__.'/fixtures/generate-report-v1.serialized',
    serialize(new GenerateReport('report-legacy')),
);
```

Metti la fixture sotto controllo di versione e caricala dai test della nuova versione. Rigenerarla con la nuova versione a ogni esecuzione non verificherebbe più la retrocompatibilità, quindi mantienila fissa.

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

namespace Acme\Reports\Tests;

use Acme\Reports\Jobs\GenerateReport;

class JobCompatibilityTest extends TestCase
{
    public function test_new_payload_keeps_explicit_values(): void
    {
        $job = unserialize(serialize(new GenerateReport('report-001', 'json')));

        $this->assertInstanceOf(GenerateReport::class, $job);
        $this->assertSame('report-001', $job->reportId);
        $this->assertSame('json', $job->format);
        $this->assertSame(2, $job->payloadVersion);
    }

    public function test_default_format_is_omitted_but_new_version_is_saved(): void
    {
        $values = (new GenerateReport('report-001'))->__serialize();

        $this->assertArrayNotHasKey('format', $values);
        $this->assertSame(2, $values['payloadVersion']);
    }

    public function test_old_payload_uses_legacy_defaults(): void
    {
        $job = unserialize(file_get_contents(
            __DIR__.'/fixtures/generate-report-v1.serialized',
        ));

        $this->assertInstanceOf(GenerateReport::class, $job);
        $this->assertSame('report-legacy', $job->reportId);
        $this->assertSame('csv', $job->format);
        $this->assertSame(1, $job->payloadVersion);
    }
}
```

Il `TestCase` di questo esempio è la classe base che prepari nell'[ambiente di test del pacchetto](https://kawax.biz/it/advanced/package-testing.md). Passa a `unserialize()` solo fixture che gestisci tu, mai input esterni.

Esegui inoltre l'`handle()` del vecchio job ripristinato con un exporter di test e verifica l'output e i risultati salvati. Se passi modelli, verifica separatamente aggiornamento, eliminazione, connessione e nuovo recupero delle relazioni e, sulla connessione di coda effettivamente usata, l'accodamento dopo il commit e i nuovi tentativi.

## Aggiornare il lato di lettura prima di accodare il nuovo formato

**1. Preparare una release in grado di leggere il vecchio formato**

Distribuisci prima un codice che mantiene il vecchio nome di classe e l'interpretazione dei vecchi valori e che sa leggere anche il nuovo formato. Con le fixture verifica non solo il ripristino ma anche il risultato dell'elaborazione.

**2. Verificare che non restino vecchi worker**

Includi la sostituzione dei worker nella procedura di deploy dell'applicazione che usa il pacchetto. Con un normale `queue:work`, `php artisan queue:restart` chiede l'uscita dopo il job in corso, e verifichi che il process manager avvii la nuova versione. Il segnale di riavvio usa la cache.

**3. Attivare l'accodamento del nuovo formato**

Quando il lato di lettura lo supporta, passa all'accodamento del nuovo formato, ad esempio con un feature flag. Se la sola distribuzione dello stesso codice inizia già a generare il nuovo formato, serve una procedura che non lo consegni ai vecchi worker, come sospendere l'accodamento o separare code vecchie e nuove con i worker corrispondenti.

**4. Mantenere il supporto per il periodo di nuovi tentativi e rollback**

Controlla anche i nuovi tentativi dei job falliti e i job accodati in seguito all'interno delle chain. Se torni al vecchio codice dopo aver salvato il nuovo formato, verifica che il vecchio codice riesca a leggerlo. Non rimuovere il supporto di vecchie classi o vecchi formati solo perché la coda si è svuotata una volta.

`queue:restart` non è un comando che riscrive i payload salvati. Nelle note di aggiornamento del pacchetto indica i formati supportati, la sostituzione dei worker necessaria e le condizioni per dismettere il vecchio formato. Registra la stessa modifica anche nella [gestione della compatibilità delle versioni](https://kawax.biz/it/advanced/package-versioning.md) e nella [gestione del changelog](https://kawax.biz/it/advanced/package-changelog.md).

## Fonti primarie consultate

* [Documentazione ufficiale di Laravel: struttura dei job e iniezione delle dipendenze](https://github.com/laravel/docs/blob/13.x/queues.md#class-structure)
* [Documentazione ufficiale di Laravel: accodamento dopo il commit](https://github.com/laravel/docs/blob/13.x/queues.md#jobs-and-database-transactions)
* [Documentazione ufficiale di Laravel: worker e deploy](https://github.com/laravel/docs/blob/13.x/queues.md#queue-workers-and-deployment)
* [Documentazione ufficiale di Laravel: gestione dei modelli eliminati](https://github.com/laravel/docs/blob/13.x/queues.md#ignoring-missing-models)
* [Queue: creazione dei payload di oggetto](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Queue/Queue.php)
* [Queueable: composizione del trait dei job](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Queue/Queueable.php)
* [SerializesModels: salvataggio e ripristino delle proprietà](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Queue/SerializesModels.php)
* [SerializesAndRestoresModelIdentifiers: sostituzione e nuovo recupero dei modelli](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php)
* [CallQueuedHandler: ripristino ed esecuzione](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Queue/CallQueuedHandler.php)
* [QueueFake: salvataggio e ripristino nel fake](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Support/Testing/Fakes/QueueFake.php)
* [Test ufficiali di Laravel: serializzazione dei modelli](https://github.com/laravel/framework/blob/v13.42.0/tests/Integration/Queue/ModelSerializationTest.php)


## Related topics

- [Testare pacchetti Laravel con Orchestra Testbench](https://kawax.biz/it/advanced/package-testing.md)
- [Argomenti avanzati](https://kawax.biz/it/advanced/index.md)
- [Registrazione e manutenzione dei task pianificati dei pacchetti](https://kawax.biz/it/advanced/package-scheduling.md)
- [Decorazione dei servizi ed extend nei pacchetti](https://kawax.biz/it/advanced/package-service-decoration.md)
- [Hook di risoluzione dei servizi nei pacchetti](https://kawax.biz/it/advanced/package-service-resolution.md)


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