Skip to Content

Chapitre 12.3 — Scheduler

Partie 12 : Temps réel & async — Chapitre 3/7 Version de référence : Symfony 7.4 (composant Scheduler).

⏱️ TL;DR

  • Le Scheduler planifie des tâches récurrentes dans l’application (pas dans le cron de l’OS) : on déclare un planning en PHP, versionné et testable.
  • Il s’appuie sur Messenger : un planning émet des messages à intervalle régulier, traités par leurs handlers habituels.
  • Un #[AsSchedule] fournit une Schedule composée de RecurringMessage : every('1 day', ...), ou une expression cron cron('0 8 * * *', ...).
  • Un worker consomme le transport du scheduler : php bin/console messenger:consume scheduler_default.
  • Avantage sur le cron OS : dans le code (versionné, testable, portable), pas de crontab à maintenir sur chaque serveur.
  • Pour un dev Next.js : c’est un cron applicatif (façon node-cron intégré), mais branché sur votre file de jobs.

🎯 Objectifs

  • Déclarer un planning de tâches récurrentes.
  • Utiliser intervalles et expressions cron.
  • Faire tourner le scheduler via un worker.
  • Comparer au cron de l’OS.

1. Le besoin : des tâches qui reviennent

BookLab a des tâches périodiques : envoyer les rappels de réservation la veille, purger les créneaux passés, recalculer des statistiques la nuit. Historiquement, on branchait le cron de l’OS sur des commandes (bin/console). Le Scheduler de Symfony fait ça dans l’application, de façon versionnée et testable.

2. Installer et déclarer un planning

composer require symfony/scheduler

Un planning est une classe #[AsSchedule] qui retourne une Schedule :

// src/Scheduler/MainSchedule.php declare(strict_types=1); namespace App\Scheduler; use App\Message\SendDailyReminders; use App\Message\PurgePastSlots; use Symfony\Component\Scheduler\Attribute\AsSchedule; use Symfony\Component\Scheduler\RecurringMessage; use Symfony\Component\Scheduler\Schedule; use Symfony\Component\Scheduler\ScheduleProviderInterface; #[AsSchedule] final class MainSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { return (new Schedule())->add( // tous les jours à 8h : envoyer les rappels RecurringMessage::cron('0 8 * * *', new SendDailyReminders()), // toutes les heures : purger les créneaux passés RecurringMessage::every('1 hour', new PurgePastSlots()), ); } }

Chaque RecurringMessage associe une fréquence à un message Messenger. Quand l’échéance arrive, le message est dispatché et traité par son handler (chapitre 12.1) — la logique de rappel vit dans un handler ordinaire, réutilisable.

3. Fréquences : intervalle ou cron

  • Intervalle : RecurringMessage::every('1 hour', ...), every('30 minutes', ...), every('1 day', ...).
  • Expression cron : RecurringMessage::cron('0 8 * * *', ...) (tous les jours à 8h), cron('*/15 * * * *', ...) (toutes les 15 min). Nécessite dragonmantank/cron-expression (tiré automatiquement).
  • On peut aussi ancrer un every à une heure précise (from), gérer le fuseau horaire, etc.

4. Faire tourner le scheduler

Le scheduler est un transport Messenger ; on le consomme avec un worker :

php bin/console messenger:consume scheduler_default -vv

Ce worker déclenche les messages aux échéances prévues. En production, il est supervisé comme tout worker (chapitre 12.2). Un worker scheduler suffit (les tâches partent ensuite dans les transports async normaux si leurs messages y sont routés).

⚠️ Piège : le Scheduler remplace le contenu du cron, mais il faut toujours un processus qui tourne (le worker scheduler). Si le worker n’est pas supervisé/actif, aucune tâche ne part. Ne croyez pas que « déclarer le planning suffit » : il faut le worker en vie. (Certains gardent un cron OS minimal qui relance le worker en cas de besoin — ceinture + bretelles.)

5. Scheduler vs cron OS

Scheduler SymfonyCron OS
dans le code (versionné)crontab du serveur
Testableoui (unitaire)non
Portableoui (même sur tous les envs)à recréer par serveur
Dépendanceun worker en viele daemon cron
Visibilitédans l’app (profiler, logs)éparpillée

Le Scheduler gagne pour la maintenabilité (tout est dans le repo). On garde parfois un cron OS très simple comme filet (relancer le worker), mais la logique de planification vit dans l’application.

💡 Pour un dev Next.js : le Scheduler ≈ un node-cron intégré au serveur, mais couplé à votre file de jobs (Messenger). L’avantage vs une crontab serveur : c’est dans le code, donc versionné, testé et déployé avec l’app — comme vous préférez gérer vos tâches planifiées côté infra-as-code plutôt qu’en éditant des crontabs à la main.

✏️ Exercices

Exercice 1. Déclarez un planning qui envoie un rapport hebdomadaire (message SendWeeklyReport) chaque lundi à 9h.

✅ Solution

#[AsSchedule] final class MainSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { return (new Schedule())->add( RecurringMessage::cron('0 9 * * 1', new SendWeeklyReport()), // lundi = 1, à 9h ); } }

Le message SendWeeklyReport sera dispatché chaque lundi à 9h et traité par son handler.

Exercice 2. Vous avez déclaré un planning mais aucune tâche ne se déclenche. Quelle est la cause la plus probable ?

✅ Solution

Le worker scheduler n’est pas en cours d’exécution : php bin/console messenger:consume scheduler_default doit tourner (et être supervisé en prod). Déclarer la Schedule ne suffit pas — il faut un processus qui la consomme et déclenche les messages aux échéances. Vérifier aussi le fuseau horaire et l’expression cron.

Exercice 3. Deux avantages du Scheduler sur une crontab serveur ?

✅ Solution

(1) Versionné et testable : la planification vit dans le code (repo), testable unitairement, déployée avec l’app — pas de crontab à maintenir à la main sur chaque serveur. (2) Portable et visible : identique sur tous les environnements, intégrée aux logs/profiler de l’app, et couplée à la file de jobs (Messenger). (Inconvénient : nécessite un worker en vie.)

🧠 Quiz de révision

1. Où le Scheduler planifie-t-il les tâches ?

Dans l’application (un planning déclaré en PHP, versionné/testable), pas dans le cron de l’OS. Il s’appuie sur Messenger.

2. Comment déclare-t-on une tâche quotidienne à 8h ?

RecurringMessage::cron('0 8 * * *', new MonMessage()) dans une Schedule fournie par une classe #[AsSchedule].

3. Comment le scheduler s’exécute-t-il ?

Via un worker : php bin/console messenger:consume scheduler_default, supervisé en production.

4. Que se passe-t-il si le worker scheduler n’est pas actif ?

Aucune tâche ne se déclenche : déclarer le planning ne suffit pas, il faut le worker en vie.

5. Un avantage clé du Scheduler sur le cron OS ?

La planification est dans le code (versionnée, testable, portable, déployée avec l’app), au lieu d’une crontab par serveur.


Prochain chapitre : 12.4 — Mailer & Notifier — e-mails et notifications multi-canal.