Passer au contenu principal
Pour ajouter une synchronisation périodique ou un heartbeat à un package, séparez la fourniture du traitement de son enregistrement dans le planificateur. La simple installation du package ne doit pas ajouter de tâches périodiques à l’application cliente : concevez-le pour que l’utilisateur choisisse l’activation, la fréquence d’exécution et le comportement sur plusieurs serveurs. Cette page examine le code source officiel de Laravel Framework v13.39.0 et applique les opérations de base de la Planification de tâches au développement de packages. Pour le chargement des providers, consultez Fonctionnement interne de la découverte automatique des packages ; pour la publication de la configuration, consultez Fusion et cache de la configuration des packages.

Enregistrer après la résolution de Schedule

Dans une application Laravel classique, FoundationServiceProvider enregistre Schedule::class en tant que singleton. Sa création appelle resolveConsoleSchedule() du Console Kernel, qui applique le fuseau horaire de l’application et le store de cache dédié au planificateur. Le package doit étendre ce Schedule partagé, et non se contenter de créer son propre new Schedule(). Le provider suivant suppose que Acme\Courier\ correspond à src/ via le PSR-4 de Composer et que le package contient config/courier.php.
callAfterResolving() attend la prochaine résolution si le service n’est pas encore résolu ; s’il l’est déjà, il le récupère et appelle aussi le callback immédiatement. Ainsi, l’enregistrement fonctionne même si un autre provider a utilisé Schedule auparavant. Pour plus de détails, consultez Hooks de résolution des services d’un package. runningInConsole() ne détecte pas une commande Artisan précise. Dans cet exemple, l’utilisation de Schedule via HTTP est exclue. Par ailleurs, le provider qui enregistre le hook ne doit pas lui-même être différé : si boot() ne s’exécute pas avant le moment requis, le hook ne sera pas enregistré non plus.
Enregistrer un événement et exécuter une tâche sont deux choses distinctes. Charger ce provider n’exécute pas le heartbeat sur-le-champ. L’enregistrement seul ne démarre ni cron ni schedule:work.

Activation explicite par l’utilisateur

Le fichier config/courier.php du package est désactivé par défaut.
Dans l’application cliente, publiez ou créez la configuration et passez schedule.enabled à true. Dans cet exemple, les valeurs sont lues au moment de l’enregistrement. Le fuseau horaire par défaut est UTC ; si vous en choisissez un autre, tenez compte des exécutions dupliquées ou manquées dues à l’heure d’été. La configuration étant fusionnée de manière superficielle, le registraire spécifie aussi une valeur par défaut pour chaque clé. Même si une surcharge partielle fait disparaître les autres clés du tableau schedule, l’enregistrement reste possible avec les réglages de base.

Empêcher le double enregistrement dans le même Schedule

Le fichier src/CourierSchedule.php suivant enregistre un heartbeat minimal qui écrit une entrée de log. Grâce à WeakMap, ce registraire n’ajoute la tâche qu’une seule fois à un même objet Schedule.
Comme ce registraire est un singleton, la vérification d’enregistrement est partagée même s’il est récupéré à nouveau depuis le hook du provider. En revanche, il peut s’enregistrer dans un nouveau Schedule. Évitez un indicateur statique « déjà enregistré » à l’échelle du processus, qui exclurait aussi les nouveaux conteneurs et les Schedule des tests. Schedule::call(), exec() et job() ajoutent l’événement à un tableau. Ce ne sont pas des API qui recherchent un événement de même nom pour le remplacer ou dédupliquer.
withoutOverlapping() ne corrige pas un double enregistrement. Si la même tâche est enregistrée deux fois, l’événement suivant peut s’exécuter une fois l’exécution précédente terminée et le verrou libéré. onOneServer() n’élimine pas non plus les doubles enregistrements.Le WeakMap ci-dessus n’empêche que les doublons de ce registraire. Si l’utilisateur ajoute la même tâche dans routes/console.php, désactivez l’enregistrement automatique du package et confiez l’enregistrement à un seul endroit.

Le nom de l’événement affecte aussi la compatibilité des verrous

name() est un alias de description(), mais son effet sur le nom du verrou dépend du type d’événement. Voici les noms de mutex par défaut. Pour un CallbackEvent, attribuez un nom non vide avant withoutOverlapping() ou onOneServer(). Si aucun nom n’est défini, ces méthodes lèvent une exception. job() utilise généralement le nom de la classe du job ou similaire comme nom initial. Cependant, si vous enregistrez le même job avec des arguments ou des cibles différents, indiquez explicitement un nom par cible. Changer seulement les arguments ne produit pas automatiquement un nom initial différent.
SyncCatalogJob est un exemple de classe de job fournie par le package. Pour l’implémentation des jobs, consultez Queues et jobs. Selon le type, modifier le nom, la commande ou l’expression cron change aussi l’identifiant du verrou. Lors d’un déploiement où coexistent des processus de l’ancienne et de la nouvelle version, un même traitement peut acquérir des verrous différents. Ne traitez pas cela comme un simple changement de libellé : gérez le nom de la tâche comme une spécification publique stable.

Distinguer onOneServer et withoutOverlapping

  • onOneServer() choisit le serveur chargé de l’exécution pour cet horaire. Le CacheSchedulingMutex examiné ajoute l’heure et la minute au nom de mutex de l’événement et le conserve avec une expiration d’une heure. C’est distinct du verrou anti-chevauchement libéré à la fin de la tâche.
  • withoutOverlapping() empêche les chevauchements tant que l’exécution précédente se poursuit. L’expiration indiquée est en minutes, avec 1 440 minutes par défaut. Une expiration trop courte fait expirer le verrou avant la fin du traitement.
  • Lorsque job() envoie le job dans une queue, la fin de l’événement planifié ne signifie pas la fin du traitement du job. Concevez séparément le contrôle des doublons côté worker, par exemple avec les jobs uniques ou le middleware WithoutOverlapping.
Pour les utilisateurs qui activent onOneServer(), indiquez comme prérequis une configuration de cache partagé database, memcached, dynamodb ou redis, comme le précise la documentation officielle. Les verrous ne garantissent pas une exécution exactly-once du traitement métier : celui-ci doit aussi supporter une ré-exécution sans dommage.

Résoudre les dépendances lourdes à l’exécution

Le LoggerInterface du heartbeat ci-dessus est résolu par CallbackEvent via call() du conteneur au moment de l’exécution de l’événement. Cette configuration n’écrit pas de log et ne crée pas de client lors de l’enregistrement du hook. Éviter les connexions externes et la logique métier dans le constructeur est également important pour la résolution des dépendances des commandes Artisan. Par ailleurs, call() et job() étant des CallbackEvent, runInBackground() n’est pas disponible. Déléguez les traitements longs à une queue ou, si nécessaire, choisissez l’exécution en arrière-plan de command().

Ne pas s’approprier la configuration globale de l’application

Schedule::useCache() modifie le store de l’objet mutex détenu par Schedule. Ce réglage ne concerne pas uniquement les événements du package : il affecte aussi les événements qui partagent le même mutex. Ne l’appelez pas inconditionnellement dans le boot() du package ; traitez-le comme un réglage choisi par l’application cliente. De même, le package ne doit pas modifier de lui-même Schedule::alwaysOnOneServer(), la mise en pause du planificateur ou la politique d’exécution en mode maintenance.

Recréer Schedule après un changement de configuration

Dans l’exemple, enabled, l’expression cron et le fuseau horaire sont des réglages lus à l’enregistrement. Modifier simplement les valeurs de config() pour un Schedule déjà créé n’ajoute ni ne supprime d’événements et ne relit pas la configuration. La vérification par WeakMap n’est pas non plus destinée à reconfigurer. Pour un déploiement utilisant le cache de configuration, reconstruisez le cache et vérifiez schedule:list dans un processus qui charge la nouvelle configuration. Pour les planificateurs de longue durée ou un schedule:run en train d’exécuter des tâches de moins d’une minute, vérifiez aussi que l’ancien code et l’ancienne configuration ne subsistent pas. Pour l’interruption des tâches sub-minute lors d’un déploiement, consultez Planification de tâches.

Tester l’enregistrement séparément de l’exécution

Voici un exemple de tests/CourierScheduleTest.php utilisant l’autoload Composer du package et PHPUnit. Les mutex sont remplacés par des doublures de test afin de vérifier le résultat de l’enregistrement sans cache externe ni véritable cron.
Ce test est un test unitaire du registraire. Vérifiez séparément la découverte automatique du provider, la fusion de la configuration et l’intégration au Console Kernel avec Orchestra Testbench ou une application de vérification. Ne lancez pas schedule:run pour exécuter toutes les tâches de l’application cliente dans le seul but de faire passer les tests d’enregistrement. En séparant les tests du traitement lui-même et les tests d’intégration du planificateur dans un environnement contrôlé, vous évitez les effets de bord de tâches périodiques sans rapport.

Pages associées

Planification de tâches

Fréquence d’exécution, mise en place de cron, délégation aux queues et exploitation lors des déploiements.

Hooks de résolution des services d'un package

Application aux services non résolus et déjà résolus, et points d’attention lors d’un nouvel enregistrement.

Sources primaires consultées

Dernière modification le 1 novembre 2026