Passer au contenu principal
Faites en sorte que les jobs mis en file avant une mise à jour du package puissent encore être traités après. Ce que la file d’attente stocke n’est pas le code à exécuter, mais le nom de la classe du job et son état. En plus des tests unitaires du nouveau code, il vous faut des tests qui restaurent avec le nouveau code un état sauvegardé par l’ancien code.
Cette page cible Laravel 13 et vérifie l’implémentation du Framework v13.42.0. Elle suppose les bases des files d’attente et les tests de package. La conception de la compatibilité ci-dessous est une politique côté package. Laravel ne fournit aucune fonctionnalité qui migre automatiquement vos propres formats de payload.

Le code change entre la sauvegarde et l’exécution

Pour les jobs objet, Queue::createObjectPayload() exécute serialize(clone $job) et place le résultat dans data.command du payload. Lorsque le chiffrement est activé, la même sérialisation a lieu avant. Côté worker, CallQueuedHandler::getCommand() applique unserialize() aux données stockées et transmet le job restauré au Dispatcher. Un handle() classique s’exécute avec le code et le conteneur de services dont dispose le worker à ce moment-là. Si vous ne redémarrez pas les workers de longue durée, l’ancien code continue de tourner après la mise à jour. Avec un déploiement progressif, envisagez non seulement « ancien émetteur avec nouveau worker », mais aussi « nouvel émetteur avec ancien worker ». Ne jugez pas le renommage ou la suppression d’une classe uniquement selon que les utilisateurs l’appellent directement. Les jobs en attente, différés ou en attente de nouvelle tentative, ainsi que la durée de conservation des jobs échoués, font aussi partie de la compatibilité.

Ce que SerializesModels sauvegarde

Illuminate\Foundation\Queue\Queueable utilise SerializesModels en interne. En plus du remplacement des modèles, il fournit __serialize(), qui énumère les propriétés du job lui-même, et __unserialize(), qui remet les valeurs en place.

Les valeurs égales à la valeur par défaut déclarée ne sont pas sauvegardées

__serialize() exclut les propriétés statiques et non initialisées, et omet aussi la sauvegarde lorsque la valeur actuelle est strictement égale à la valeur par défaut déclarée de la propriété.
Si cette propriété vaut toujours 'csv', le tableau sauvegardé ne contient pas format. Si une version ultérieure change la valeur par défaut déclarée en 'json', 'json' peut aussi s’appliquer aux anciens jobs. Ce n’est pas un changement cosmétique de valeur par défaut, mais un changement dans la lecture des anciennes données.

Le constructeur n’est pas rappelé lors de la restauration

__unserialize() assigne des valeurs aux propriétés qui existent dans la classe actuelle et qui ont une clé dans le tableau sauvegardé. Il n’exécute aucun complément ni aucune validation des entrées via le constructeur.
Le 'csv' de cet exemple est la valeur par défaut d’un argument du constructeur. Ce n’est pas la valeur par défaut déclarée de la propriété elle-même, donc elle ne complète pas la valeur lorsque vous restaurez un ancien payload sans format. Si une propriété typée ajoutée reste non initialisée, l’accès ultérieur échoue. Par ailleurs, la clé d’une propriété public est son nom, celle d’une propriété protected porte un préfixe de visibilité, et celle d’une propriété private inclut aussi le nom de la classe. Vérifiez aussi avec d’anciens payloads un passage de public à private.
SerializesModels énumère les propriétés du job. Ne supposez pas que la même conversion s’applique récursivement à l’intérieur de DTO arbitraires ou aux modèles enfouis dans des tableaux. Si vous transmettez des objets autres que des modèles, leurs classes et formats de sauvegarde font aussi partie de la compatibilité.

Séparer les identifiants des services d’exécution

Stockez dans le job les identifiants et options nécessaires à l’exécution, et injectez dans handle() les services d’exécution tels que les clients d’API ou les exportateurs. Si vous passez un service au constructeur et le conservez, son état interne peut aussi être sauvegardé. L’exemple suivant ajoute un choix de format et une version de payload à un job du même nom qui, dans l’ancienne version, ne contenait que reportId. ReportExporter est un contrat que le package fournit et lie dans le conteneur, avec export(string $reportId, string $format): void.
Cette conception sépare le sens des valeurs manquantes des valeurs nouvellement mises en file.
  • Si un ancien payload n’a pas de format, la valeur par défaut déclarée 'csv' est utilisée.
  • Si un ancien payload n’a pas de payloadVersion, elle est traitée comme la valeur par défaut déclarée 1.
  • Pour les nouveaux jobs, le constructeur fixe payloadVersion à 2. Comme elle diffère de la valeur par défaut déclarée, cette valeur est sauvegardée.
  • Si le format d’un nouveau job est 'json', cette valeur est aussi sauvegardée. 'csv' est omis, conservez donc à l’avenir la valeur par défaut déclarée 'csv'.
Si vous voulez plus tard changer uniquement le format par défaut des nouveaux jobs, vous pouvez modifier la valeur par défaut de l’argument du constructeur et l’affectation, et les examiner séparément de la valeur par défaut de la propriété utilisée pour les anciennes données. Gérez la version de payload non comme le numéro de version du package, mais comme la génération du format de sauvegarde et de son interprétation.
Ajouter un numéro de version ne suffit pas à assurer la compatibilité. La nouvelle version de cet exemple sait aussi traiter l’ancien format, mais rien ne garantit que les anciens workers comprennent le nouveau format ou payloadVersion. Il vous faut aussi l’ordre de mise à jour de l’émetteur et des workers décrit plus bas.

Distinguer la transmission de modèles des instantanés

Si vous stockez un modèle directement dans une propriété, SerializesAndRestoresModelIdentifiers le remplace par un ModelIdentifier contenant la classe, l’ID, le nom de connexion, les noms des relations chargées, etc. Lors de la restauration d’un modèle unique, il le récupère à nouveau en base avec firstOrFail() et charge les relations concernées. Ce n’est pas un mécanisme qui fige les attributs ou les données liées au moment de la mise en file.
  • Si l’enregistrement est mis à jour avant l’exécution, le modèle restauré reflète le contenu mis à jour.
  • Les contraintes appliquées au préalable aux relations ne sont pas sauvegardées, réappliquez donc les conditions nécessaires à l’exécution.
  • Excluez de la sauvegarde les relations inutiles avec withoutRelations() ou #[WithoutRelations].
  • Si le modèle a été supprimé, le job peut échouer lors de la restauration, avant son handle().
S’il est acceptable d’abandonner les jobs dont le modèle a été supprimé, vous pouvez envisager l’attribut officiel #[DeleteWhenMissingModels]. Toutefois, le package ne doit pas faire de l’abandon le comportement par défaut de lui-même, mais exposer la politique d’échec aux utilisateurs. Si vous ne transmettez qu’un ID et faites la recherche dans handle(), vous contrôlez vous-même l’échec ou l’omission de cette recherche. Si vous avez besoin du contenu au moment de la mise en file, sauvegardez explicitement les valeurs nécessaires, ou enregistrez en base par exemple une demande de rapport immuable et transmettez son ID. Distinguez le simple passage d’un modèle de la sauvegarde d’un instantané des données métier.

La mise en file après le commit est un autre problème

Lorsque vous traitez des enregistrements créés dans une transaction, empêchez les workers de les lire avant le commit.
afterCommit() attend le commit de la transaction parente ouverte avant de mettre en file, et abandonne la mise en file en cas de rollback. Il ne résout ni la compatibilité des noms de classe et des propriétés, ni l’exécution en double de traitements externes.

Séparer les fakes des tests d’anciens payloads

Un Queue::fake() classique enregistre les mises en file. Un assertPushed() qui réussit ne confirme pas à lui seul la sauvegarde et la restauration, ni que la nouvelle version traite les anciens payloads. Vous pouvez vérifier un aller-retour avec la classe actuelle ainsi.
serializeAndRestore() exécute serialize() et unserialize() dans le fake. Il ne vérifie cependant ni la mise en file vers un vrai backend, ni l’exécution par un worker, ni la génération par l’ancien code.

Conserver des fixtures générées par l’ancienne version

Tant que l’ancien GenerateReport ne contient que reportId, générez un job fixe sans données réelles. Le code de préparation suivant s’exécute une fois dans l’environnement de test de l’ancienne version. Créez tests/fixtures/ au préalable.
Placez la fixture sous contrôle de version et chargez-la depuis les tests de la nouvelle version. La régénérer avec la nouvelle version à chaque exécution ne vérifierait plus la rétrocompatibilité, gardez-la donc figée.
Le TestCase de cet exemple est la classe de base que vous préparez dans l’environnement de test du package. Ne passez à unserialize() que des fixtures que vous gérez vous-même, jamais d’entrées externes. Exécutez aussi le handle() de l’ancien job restauré avec un exportateur de test et vérifiez la sortie et les résultats sauvegardés. Si vous transmettez des modèles, vérifiez séparément la mise à jour, la suppression, la connexion et la récupération des relations et, sur la connexion de file réellement utilisée, la mise en file après le commit et les nouvelles tentatives.

Mettre à jour le côté lecteur avant de mettre en file le nouveau format

1

Préparer une version capable de lire l'ancien format

Distribuez d’abord un code qui conserve l’ancien nom de classe et l’interprétation des anciennes valeurs, tout en sachant lire le nouveau format. Avec les fixtures, vérifiez non seulement la restauration mais aussi le résultat du traitement.
2

Vérifier qu'aucun ancien worker ne subsiste

Intégrez le remplacement des workers à la procédure de déploiement de l’application utilisatrice. Avec un queue:work classique, php artisan queue:restart demande l’arrêt après le job en cours, et vous vérifiez que le gestionnaire de processus démarre la nouvelle version. Le signal de redémarrage passe par le cache.
3

Activer la mise en file du nouveau format

Une fois le côté lecteur prêt, basculez vers la mise en file du nouveau format, par exemple avec un feature flag. Si la simple distribution du même code commence aussi à générer le nouveau format, il faut une procédure qui ne le livre pas aux anciens workers, comme suspendre la mise en file ou séparer anciennes et nouvelles files avec les workers correspondants.
4

Maintenir le support pendant la période de nouvelles tentatives et de rollback

Vérifiez aussi les nouvelles tentatives de jobs échoués et les jobs mis en file plus tard au sein de chaînes. Si vous revenez à l’ancien code après avoir sauvegardé le nouveau format, vérifiez que l’ancien code peut le lire. Ne supprimez pas le support des anciennes classes ou formats simplement parce que la file a été vide une fois.
queue:restart n’est pas une commande qui réécrit les payloads sauvegardés. Dans les notes de mise à jour du package, indiquez les formats pris en charge, le remplacement de workers nécessaire et les conditions de retrait de l’ancien format. Consignez le même changement dans la gestion de la compatibilité des versions et la gestion du changelog.

Sources primaires consultées

Dernière modification le 12 novembre 2026