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 uneSchedulecomposée deRecurringMessage:every('1 day', ...), ou une expression croncron('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-croninté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/schedulerUn 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écessitedragonmantank/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 -vvCe 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 Symfony | Cron OS | |
|---|---|---|
| Où | dans le code (versionné) | crontab du serveur |
| Testable | oui (unitaire) | non |
| Portable | oui (même sur tous les envs) | à recréer par serveur |
| Dépendance | un worker en vie | le 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-croninté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.