Naar hoofdinhoud gaan
Zorg dat jobs die vóór een package-update in de queue zijn gezet, daarna nog verwerkt kunnen worden. De queue slaat niet de uit te voeren code op, maar de naam van de jobklasse en haar toestand. Naast unittests voor de nieuwe code heb je tests nodig die een door de oude code opgeslagen toestand met de nieuwe code herstellen.
Deze pagina richt zich op Laravel 13 en controleert de implementatie van Framework v13.42.0. Ze gaat uit van de basis van queues en het testen van packages. Het compatibiliteitsontwerp hieronder is beleid aan de kant van het package. Laravel heeft geen functie die je eigen payloadformaten automatisch migreert.

De code verandert tussen opslaan en uitvoeren

Bij objectjobs voert Queue::createObjectPayload() serialize(clone $job) uit en zet het resultaat in data.command van de payload. Bij versleuteling vindt dezelfde serialisatie daarvoor plaats. Aan de workerkant voert CallQueuedHandler::getCommand() unserialize() uit op de opgeslagen data en geeft de herstelde job door aan de Dispatcher. Een gewone handle() draait met de code en de service container die de worker op dat moment heeft. Als je langlopende workers niet herstart, blijft na de update de oude code draaien. Houd bij rolling deploys niet alleen rekening met “oude dispatcher met nieuwe worker”, maar ook met “nieuwe dispatcher met oude worker”. Beoordeel het hernoemen of verwijderen van een klasse niet alleen op de vraag of gebruikers die klasse direct aanroepen. Wachtende, uitgestelde en op een nieuwe poging wachtende jobs, en de bewaartermijn van mislukte jobs, horen ook bij compatibiliteit.

Wat SerializesModels opslaat

Illuminate\Foundation\Queue\Queueable gebruikt intern SerializesModels. Naast het vervangen van modellen levert het __serialize(), dat de properties van de job zelf opsomt, en __unserialize(), dat de waarden terugzet.

Waarden gelijk aan de gedeclareerde standaardwaarde worden niet opgeslagen

__serialize() sluit statische en niet-geïnitialiseerde properties uit en slaat het opslaan ook over wanneer de huidige waarde strikt gelijk is aan de gedeclareerde standaardwaarde van de property.
Blijft deze property 'csv', dan bevat de opgeslagen array geen format. Verandert een latere release de gedeclareerde standaardwaarde in 'json', dan kan 'json' ook voor oude jobs gelden. Dat is geen cosmetische wijziging van de standaardwaarde, maar een wijziging in hoe oude data wordt gelezen.

De constructor wordt bij herstellen niet opnieuw aangeroepen

__unserialize() wijst waarden toe aan properties die in de huidige klasse bestaan en waarvoor de opgeslagen array een sleutel heeft. Aanvulling of validatie van invoer via de constructor vindt niet plaats.
De 'csv' in dit voorbeeld is de standaardwaarde van een constructorargument. Het is niet de gedeclareerde standaardwaarde van de property zelf, dus vult het de waarde niet aan wanneer je een oude payload zonder format herstelt. Blijft een toegevoegde getypeerde property niet-geïnitialiseerd, dan mislukt een latere toegang. Verder is de sleutel van een public property haar naam, die van een protected property heeft een prefix voor de zichtbaarheid en die van een private property bevat ook de klassenaam. Controleer ook een wijziging van public naar private met oude payloads.
SerializesModels somt de properties van de job op. Ga er niet van uit dat dezelfde omzetting recursief plaatsvindt binnen willekeurige DTO’s of voor modellen diep in arrays. Geef je andere objecten dan modellen door, dan horen hun klassen en opslagformaten ook bij compatibiliteit.

Identifiers en uitvoerende services scheiden

Sla de voor uitvoering benodigde identifiers en opties op in de job, en injecteer uitvoerende services zoals API-clients of exporters in handle(). Geef je een service aan de constructor door en bewaar je die, dan kan ook haar interne toestand worden opgeslagen. Het volgende voorbeeld voegt een formaatkeuze en een payloadversie toe aan een job met dezelfde naam die in de oude versie alleen reportId had. ReportExporter is een contract dat het package levert en in de container bindt, met export(string $reportId, string $format): void.
Dit ontwerp scheidt de betekenis van ontbrekende waarden van de waarden die nieuw worden gedispatcht.
  • Heeft een oude payload geen format, dan wordt de gedeclareerde standaardwaarde 'csv' gebruikt.
  • Heeft een oude payload geen payloadVersion, dan wordt die behandeld als de gedeclareerde standaardwaarde 1.
  • Bij nieuw aangemaakte jobs zet de constructor payloadVersion op 2. Omdat die afwijkt van de gedeclareerde standaardwaarde, wordt deze waarde opgeslagen.
  • Is de format van een nieuwe job 'json', dan wordt ook die waarde opgeslagen. 'csv' wordt overgeslagen, dus houd de gedeclareerde standaardwaarde 'csv' voortaan aan.
Wil je later alleen het standaardformaat van nieuwe jobs wijzigen, dan kun je de standaardwaarde van het constructorargument en de toewijzing aanpassen en die los bekijken van de propertystandaardwaarde voor oude data. Beheer de payloadversie niet als releasenummer van het package, maar als generatie van het opslagformaat en de interpretatie ervan.
Alleen een versienummer toevoegen levert geen compatibiliteit op. De nieuwe versie in dit voorbeeld kan ook het oude formaat verwerken, maar niets garandeert dat oude workers de nieuwe format of payloadVersion begrijpen. Je hebt ook de hieronder beschreven volgorde voor het bijwerken van dispatcher en workers nodig.

Doorgegeven modellen onderscheiden van snapshots

Sla je een model direct op in een property, dan vervangt SerializesAndRestoresModelIdentifiers het door een ModelIdentifier met klasse, ID, verbindingsnaam, namen van geladen relaties enzovoort. Bij het herstellen van één model haalt het dit opnieuw uit de database met firstOrFail() en laadt het de betreffende relaties. Dit is geen mechanisme dat attributen of gerelateerde data vastlegt op het moment van dispatchen.
  • Wordt het record vóór de uitvoering bijgewerkt, dan ziet het herstelde model de bijgewerkte inhoud.
  • Vooraf op relaties toegepaste beperkingen worden niet opgeslagen, dus pas de benodigde voorwaarden bij uitvoering opnieuw toe.
  • Sluit overbodige relaties uit van opslag met withoutRelations() of #[WithoutRelations].
  • Is het model verwijderd, dan kan de job al bij het herstellen mislukken, vóór zijn handle().
Mogen jobs met een verwijderd model worden weggegooid, dan kun je het officiële #[DeleteWhenMissingModels] overwegen. Het package moet weggooien echter niet op eigen houtje de standaard maken, maar het faalbeleid aan gebruikers tonen. Geef je alleen een ID door en zoek je in handle(), dan bepaal je zelf hoe die zoekactie faalt of wordt overgeslagen. Heb je de inhoud op het moment van dispatchen nodig, sla de benodigde waarden dan expliciet op, of leg bijvoorbeeld een onveranderlijk rapportverzoek vast in de database en geef het ID daarvan door. Onderscheid het simpelweg doorgeven van een model van het opslaan van een snapshot van bedrijfsdata.

Dispatchen na de commit is een apart probleem

Verwerk je records die binnen een transactie zijn aangemaakt, zorg dan dat workers ze niet vóór de commit gaan lezen.
afterCommit() wacht met dispatchen tot de open bovenliggende transactie is gecommit en gooit de dispatch weg bij een rollback. Het lost de compatibiliteit van klassenamen en properties of het dubbel uitvoeren van externe verwerking niet op.

Fakes en tests van oude payloads scheiden

Een gewone Queue::fake() registreert dispatches. Een geslaagde assertPushed() bevestigt op zich niet het opslaan en herstellen, of dat de nieuwe versie oude payloads verwerkt. Een round trip met de huidige klasse controleer je zo.
serializeAndRestore() voert serialize() en unserialize() uit binnen de fake. Het controleert echter niet het dispatchen naar een echte backend, de uitvoering door een worker of het genereren door oude code.

Met de oude versie gegenereerde fixtures bewaren

Zolang de oude GenerateReport alleen reportId heeft, genereer je een vaste job zonder echte data. De volgende voorbereidingscode voer je één keer uit in de testomgeving van de oude versie. Maak tests/fixtures/ vooraf aan.
Zet de fixture onder versiebeheer en laad haar vanuit de tests van de nieuwe versie. Genereer je haar bij elke testrun opnieuw met de nieuwe versie, dan test je geen achterwaartse compatibiliteit meer, dus houd haar vast.
De TestCase in dit voorbeeld is de basisklasse die je in de testomgeving voor packages opzet. Geef aan unserialize() alleen fixtures die je zelf beheert, nooit externe invoer. Voer daarnaast handle() van de herstelde oude job uit met een test-exporter en controleer de uitvoer en de opgeslagen resultaten. Geef je modellen door, controleer dan apart bijwerken, verwijderen, verbinding en het opnieuw ophalen van relaties, en op de queueverbinding die je echt gebruikt ook dispatchen na de commit en nieuwe pogingen.

De lezende kant bijwerken voordat je het nieuwe formaat dispatcht

1

Een release voorbereiden die het oude formaat kan lezen

Lever eerst code die de oude klassenaam en de interpretatie van oude waarden behoudt en ook het nieuwe formaat kan lezen. Controleer met fixtures niet alleen het herstellen, maar ook het verwerkingsresultaat.
2

Controleren dat er geen oude workers overblijven

Neem het vervangen van workers op in de deployprocedure van de gebruikende app. Bij een gewone queue:work vraagt php artisan queue:restart om te stoppen na de huidige job, en je controleert dat de procesmanager de nieuwe versie start. Voor het herstartsignaal wordt de cache gebruikt.
3

Het dispatchen van het nieuwe formaat inschakelen

Schakel pas over op het dispatchen van het nieuwe formaat wanneer de lezende kant het ondersteunt, bijvoorbeeld met een feature flag. Begint het uitrollen van dezelfde code al met het genereren van het nieuwe formaat, dan heb je een procedure nodig die het bij oude workers weghoudt, zoals het dispatchen pauzeren of oude en nieuwe queues met bijbehorende workers scheiden.
4

Ondersteuning behouden tijdens de periode voor nieuwe pogingen en rollback

Controleer ook nieuwe pogingen van mislukte jobs en jobs die later binnen chains worden gedispatcht. Ga je na het opslaan van het nieuwe formaat terug naar oude code, controleer dan of de oude code het kan lezen. Verwijder ondersteuning voor oude klassen of formaten niet alleen omdat de queue een keer leeg was.
queue:restart is geen commando dat opgeslagen payloads herschrijft. Vermeld in de update-instructies van het package de ondersteunde formaten, de benodigde vervanging van workers en de voorwaarden om het oude formaat af te schaffen. Leg dezelfde wijziging ook vast in versiecompatibiliteitsbeheer en changelogbeheer.

Geraadpleegde primaire bronnen

Laatst gewijzigd op 12 november 2026