Zum Hauptinhalt springen
Stellen Sie sicher, dass Jobs, die vor einem Paket-Update dispatcht wurden, auch danach verarbeitet werden können. Die Queue speichert nicht den auszuführenden Code, sondern den Namen der Job-Klasse und ihren Zustand. Neben Unit-Tests für den neuen Code brauchen Sie Tests, die einen vom alten Code gespeicherten Zustand mit dem neuen Code wiederherstellen.
Diese Seite richtet sich an Laravel 13 und prüft die Implementierung von Framework v13.42.0. Sie setzt die Grundlagen der Queues und das Testen von Paketen voraus. Das folgende Kompatibilitätsdesign ist eine Richtlinie auf Paketseite. Laravel bietet keine Funktion, die eigene Payload-Formate automatisch migriert.

Der Code ändert sich zwischen Speichern und Ausführen

Bei Objekt-Jobs führt Queue::createObjectPayload() serialize(clone $job) aus und legt das Ergebnis in data.command der Payload ab. Bei aktivierter Verschlüsselung findet davor dieselbe Serialisierung statt. Auf Worker-Seite führt CallQueuedHandler::getCommand() unserialize() auf die gespeicherten Daten aus und übergibt den wiederhergestellten Job an den Dispatcher. Ein normales handle() läuft mit dem Code und dem Service-Container, die der Worker zu diesem Zeitpunkt hat. Wenn Sie langlebige Worker nicht neu starten, läuft nach dem Update weiterhin der alte Code. Bei Rolling Deployments berücksichtigen Sie nicht nur „alte Dispatch-Seite mit neuem Worker“, sondern auch „neue Dispatch-Seite mit altem Worker“. Beurteilen Sie das Umbenennen oder Entfernen einer Klasse nicht nur danach, ob Nutzer die Klasse direkt aufrufen. Wartende, verzögerte und auf Wiederholung wartende Jobs sowie die Aufbewahrungsdauer fehlgeschlagener Jobs gehören ebenfalls zur Kompatibilität.

Was SerializesModels speichert

Illuminate\Foundation\Queue\Queueable verwendet intern SerializesModels. Neben dem Ersetzen von Modellen stellt es __serialize() bereit, das die Properties des Jobs selbst auflistet, und __unserialize(), das die Werte zurückschreibt.

Werte, die dem deklarierten Standardwert entsprechen, werden nicht gespeichert

__serialize() schließt statische und nicht initialisierte Properties aus und lässt das Speichern auch aus, wenn der aktuelle Wert strikt gleich dem deklarierten Standardwert der Property ist.
Bleibt diese Property 'csv', enthält das gespeicherte Array kein format. Ändert ein späteres Release den deklarierten Standardwert auf 'json', kann 'json' auch für alte Jobs gelten. Das ist keine kosmetische Änderung des Standardwerts, sondern eine Änderung, wie alte Daten gelesen werden.

Der Konstruktor wird beim Wiederherstellen nicht erneut aufgerufen

__unserialize() weist Properties Werte zu, die in der aktuellen Klasse existieren und für die das gespeicherte Array einen Schlüssel hat. Eine Ergänzung oder Validierung von Eingaben über den Konstruktor findet nicht statt.
Das 'csv' in diesem Beispiel ist der Standardwert eines Konstruktorarguments. Es ist nicht der deklarierte Standardwert der Property selbst und ergänzt den Wert daher nicht, wenn Sie eine alte Payload ohne format wiederherstellen. Bleibt eine neu hinzugefügte typisierte Property nicht initialisiert, schlägt ein späterer Zugriff fehl. Außerdem ist der Schlüssel einer public Property ihr Name, der einer protected Property trägt ein Präfix für die Sichtbarkeit, und der einer private Property enthält zusätzlich den Klassennamen. Prüfen Sie auch eine Änderung von public zu private mit alten Payloads.
SerializesModels listet die Properties des Jobs auf. Gehen Sie nicht davon aus, dass dieselbe Umwandlung rekursiv im Inneren beliebiger DTOs oder für Modelle tief in Arrays stattfindet. Wenn Sie andere Objekte als Modelle übergeben, gehören deren Klassen und Speicherformate ebenfalls zur Kompatibilität.

Bezeichner und ausführende Services trennen

Speichern Sie die für die Ausführung nötigen Bezeichner und Optionen im Job und injizieren Sie ausführende Services wie API-Clients oder Exporter in handle(). Übergeben Sie einen Service an den Konstruktor und behalten ihn, kann auch sein interner Zustand gespeichert werden. Das folgende Beispiel ergänzt einen gleichnamigen Job, der in der alten Version nur reportId hatte, um eine Formatwahl und eine Payload-Version. ReportExporter ist ein Contract, den das Paket bereitstellt und im Container bindet, mit export(string $reportId, string $format): void.
Dieses Design trennt die Bedeutung fehlender Werte von den neu dispatchten Werten.
  • Fehlt format in einer alten Payload, wird der deklarierte Standardwert 'csv' verwendet.
  • Fehlt payloadVersion in einer alten Payload, wird sie als deklarierter Standardwert 1 behandelt.
  • Bei neu erzeugten Jobs setzt der Konstruktor payloadVersion auf 2. Da der Wert vom deklarierten Standardwert abweicht, wird er gespeichert.
  • Ist format eines neuen Jobs 'json', wird auch dieser Wert gespeichert. 'csv' wird ausgelassen, behalten Sie daher den deklarierten Standardwert 'csv' künftig bei.
Wollen Sie später nur das Standardformat neuer Jobs ändern, können Sie den Standardwert des Konstruktorarguments und die Zuweisung ändern und dies getrennt vom Property-Standardwert für alte Daten betrachten. Verwalten Sie die Payload-Version nicht als Release-Nummer des Pakets, sondern als Generation des Speicherformats und seiner Interpretation.
Eine Versionsnummer allein bringt keine Kompatibilität. Die neue Version in diesem Beispiel kann auch das alte Format verarbeiten, aber nichts garantiert, dass alte Worker das neue format oder payloadVersion verstehen. Sie brauchen zusätzlich die weiter unten beschriebene Reihenfolge beim Aktualisieren von Dispatch-Seite und Workern.

Übergebene Modelle von Snapshots unterscheiden

Speichern Sie ein Modell direkt in einer Property, ersetzt SerializesAndRestoresModelIdentifiers es durch einen ModelIdentifier mit Klasse, ID, Verbindungsname, Namen geladener Relationen usw. Beim Wiederherstellen eines einzelnen Modells lädt es dieses mit firstOrFail() erneut aus der Datenbank und lädt die betreffenden Relationen. Dies ist kein Mechanismus, der Attribute oder verknüpfte Daten zum Zeitpunkt des Dispatchens festhält.
  • Wird der Datensatz vor der Ausführung aktualisiert, sieht das wiederhergestellte Modell den aktualisierten Inhalt.
  • Vorab angewendete Einschränkungen auf Relationen werden nicht gespeichert, wenden Sie benötigte Bedingungen daher zur Ausführungszeit erneut an.
  • Schließen Sie unnötige Relationen mit withoutRelations() oder #[WithoutRelations] vom Speichern aus.
  • Wurde das Modell gelöscht, kann der Job bereits beim Wiederherstellen fehlschlagen, vor handle().
Dürfen Jobs mit gelöschtem Modell verworfen werden, können Sie das offizielle #[DeleteWhenMissingModels] in Betracht ziehen. Das Paket sollte das Verwerfen jedoch nicht eigenmächtig zum Standard machen, sondern die Fehlerrichtlinie für Nutzer offenlegen. Übergeben Sie nur eine ID und suchen in handle(), steuern Sie Fehlschlag oder Überspringen dieser Suche selbst. Benötigen Sie den Inhalt zum Zeitpunkt des Dispatchens, speichern Sie die nötigen Werte explizit oder legen Sie etwa eine unveränderliche Report-Anfrage in der Datenbank ab und übergeben deren ID. Unterscheiden Sie das bloße Übergeben eines Modells vom Speichern eines Snapshots von Geschäftsdaten.

Dispatchen nach dem Commit ist ein separates Problem

Wenn Sie Datensätze verarbeiten, die in einer Transaktion erstellt wurden, verhindern Sie, dass Worker sie vor dem Commit lesen.
afterCommit() wartet mit dem Dispatchen bis zum Commit der offenen übergeordneten Transaktion und verwirft das Dispatchen bei einem Rollback. Es löst weder die Kompatibilität von Klassennamen und Properties noch die doppelte Ausführung externer Verarbeitung.

Fakes und Tests alter Payloads trennen

Ein normales Queue::fake() zeichnet Dispatches auf. Ein erfolgreiches assertPushed() allein bestätigt weder Speichern und Wiederherstellen noch, dass die neue Version alte Payloads verarbeitet. Einen Roundtrip mit der aktuellen Klasse können Sie so prüfen.
serializeAndRestore() führt serialize() und unserialize() innerhalb des Fakes aus. Es prüft jedoch weder das Dispatchen an ein echtes Backend noch die Ausführung durch einen Worker oder die Erzeugung durch alten Code.

Mit der alten Version erzeugte Fixtures aufbewahren

Solange das alte GenerateReport nur reportId hat, erzeugen Sie einen festen Job ohne echte Daten. Der folgende Vorbereitungscode wird einmal in der Testumgebung der alten Version ausgeführt. Legen Sie tests/fixtures/ vorher an.
Stellen Sie die Fixture unter Versionskontrolle und laden Sie sie aus den Tests der neuen Version. Würden Sie sie bei jedem Testlauf mit der neuen Version neu erzeugen, prüften Sie keine Abwärtskompatibilität mehr. Lassen Sie sie daher unverändert.
Das TestCase in diesem Beispiel ist die Basisklasse, die Sie in der Testumgebung für Pakete einrichten. Übergeben Sie unserialize() nur selbst verwaltete Fixtures und niemals externe Eingaben. Führen Sie außerdem handle() des wiederhergestellten alten Jobs mit einem Test-Exporter aus und prüfen Sie Ausgabe und gespeicherte Ergebnisse. Wenn Sie Modelle übergeben, prüfen Sie Aktualisierung, Löschung, Verbindung und erneutes Laden von Relationen separat, und auf der tatsächlich genutzten Queue-Verbindung auch das Dispatchen nach dem Commit und Wiederholungen.

Die lesende Seite vor dem Dispatchen des neuen Formats aktualisieren

1

Ein Release vorbereiten, das das alte Format lesen kann

Verteilen Sie zuerst Code, der den alten Klassennamen und die Interpretation alter Werte beibehält und zusätzlich das neue Format lesen kann. Prüfen Sie mit Fixtures nicht nur das Wiederherstellen, sondern auch das Verarbeitungsergebnis.
2

Sicherstellen, dass keine alten Worker übrig bleiben

Nehmen Sie den Austausch der Worker in das Deployment-Verfahren der nutzenden Anwendung auf. Bei einem normalen queue:work fordert php artisan queue:restart das Beenden nach dem aktuellen Job an, und Sie prüfen, dass der Prozessmanager die neue Version startet. Für das Neustartsignal wird der Cache verwendet.
3

Das Dispatchen des neuen Formats aktivieren

Schalten Sie erst auf das Dispatchen des neuen Formats um, wenn die lesende Seite es unterstützt, etwa per Feature-Flag. Beginnt schon die Verteilung desselben Codes mit dem Erzeugen des neuen Formats, brauchen Sie ein Verfahren, das es von alten Workern fernhält, etwa das Dispatchen pausieren oder alte und neue Queues mit passenden Workern trennen.
4

Den Zeitraum für Wiederholungen und Rollbacks weiter unterstützen

Prüfen Sie auch Wiederholungen fehlgeschlagener Jobs und Jobs, die später innerhalb von Chains dispatcht werden. Kehren Sie nach dem Speichern des neuen Formats zu altem Code zurück, prüfen Sie, ob der alte Code es lesen kann. Entfernen Sie die Unterstützung alter Klassen oder Formate nicht, nur weil die Queue einmal leer war.
queue:restart ist kein Befehl, der gespeicherte Payloads umschreibt. Nennen Sie in den Update-Hinweisen des Pakets die unterstützten Formate, den nötigen Austausch der Worker und die Bedingungen für das Abschaffen des alten Formats. Halten Sie dieselbe Änderung auch im Versionskompatibilitätsmanagement und im Changelog-Betrieb fest.

Herangezogene Primärquellen

Zuletzt geändert am 12. November 2026