Symfony Scheduler : planifier des tâches sans toucher à la crontab
Par Mendel · 01/10/2026 à 00:29
Publier un article à une heure précise, envoyer un récapitulatif chaque lundi, nettoyer des fichiers inutiles chaque nuit : tôt ou tard, toute application a besoin de tâches qui se déclenchent à cause du temps qui passe, et non à cause d'un clic. Depuis Symfony 6.3, le composant Scheduler permet de gérer tout ça directement dans le code. Ce blog l'utilise d'ailleurs pour deux tâches, et c'est ce retour d'expérience que je vous propose.
Pourquoi pas une simple crontab ?
La solution classique consiste à écrire une commande Symfony, puis à ajouter une ligne dans la crontab du serveur :
* * * * * cd /var/www/blog && php bin/console app:ma-commande
Ça fonctionne, mais la planification vit en dehors du projet : elle n'est pas versionnée dans Git, un nouveau serveur doit être configuré à la main, et rien dans le code n'indique qu'une commande est censée tourner toutes les minutes.
Avec le Scheduler, la planification fait partie du code. Elle est versionnée, relue en revue de code, et un seul processus suffit sur le serveur, quel que soit le nombre de tâches.
Le principe
Le Scheduler s'appuie sur le composant Messenger. Son rôle est simple : générer des messages au bon moment. Un worker, un processus qui tourne en permanence, surveille le planning et exécute chaque tâche quand son heure arrive.
L'installation tient en une ligne :
composer require symfony/scheduler
Une tâche toutes les minutes
Sur ce blog, un auteur peut programmer un article à une date future. Il faut donc vérifier régulièrement si des articles arrivent à échéance. La façon la plus simple est d'ajouter l'attribut #[AsPeriodicTask] sur une commande console :
#[AsCommand(
name: 'app:publish-scheduled-articles',
description: 'Publie les articles programmés dont la date est passée',
)]
#[AsPeriodicTask(frequency: '1 minute')]
final class PublishScheduledArticlesCommand extends Command
{
public function __construct(
private ArticleRepository $articleRepository,
private EntityManagerInterface $em,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$articles = $this->articleRepository->findScheduledToPublish(new \DateTimeImmutable());
foreach ($articles as $article) {
$article->setStatus(ArticleStatus::Published);
}
$this->em->flush();
return Command::SUCCESS;
}
}
C'est tout. La commande reste utilisable à la main, ce qui est très pratique pour la tester :
php bin/console app:publish-scheduled-articles
Et d'ailleurs, si vous lisez cet article, c'est que cette tâche a fonctionné : il a été écrit à l'avance, puis publié automatiquement par le Scheduler.
Une tâche à heure fixe
Pour une exécution à une heure précise, on utilise #[AsCronTask] avec la syntaxe cron habituelle. Ce blog supprime ainsi chaque nuit à 3h les images qui ne sont plus utilisées par aucun article :
#[AsCommand(name: 'app:clean-orphan-images')]
#[AsCronTask('0 3 * * *')]
final class CleanOrphanImagesCommand extends Command
{
// ...
}
Les expressions cron nécessitent une bibliothèque supplémentaire :
composer require dragonmantank/cron-expression
Pour vérifier le planning à tout moment, une commande liste toutes les tâches et leur prochaine exécution :
php bin/console debug:scheduler
Lancer le worker
Déclarer les tâches ne suffit pas : il faut un worker qui tourne en permanence.
php bin/console messenger:consume scheduler_default
En développement, la Symfony CLI peut le lancer automatiquement avec le serveur, grâce au fichier .symfony.local.yaml :
workers:
scheduler:
cmd: ['symfony', 'console', 'messenger:consume', 'scheduler_default']
watch: ['config', 'src']
L'option watch redémarre le worker à chaque modification du code. En production, on confie ce rôle à Supervisor, qui lance le worker au démarrage du serveur et le relance s'il s'arrête :
[program:blog-scheduler]
command=php /var/www/blog/bin/console messenger:consume scheduler_default --time-limit=3600
user=www-data
numprocs=1
autostart=true
autorestart=true
Les pièges à éviter
Quelques leçons apprises en mettant tout ça en place :
- Un seul worker. Avec
numprocs=2, chaque tâche s'exécuterait deux fois. Si vous devez vraiment en lancer plusieurs, le Scheduler propose un système de verrou. - Redémarrer le worker après chaque déploiement. Un worker garde le code en mémoire : sans redémarrage, il continue d'exécuter l'ancienne version.
- Configurer le fuseau horaire de PHP, en ligne de commande comme pour le serveur web. Sans
date.timezone, vos articles risquent d'être publiés avec quelques heures de décalage. - Prévoir un mode simulation pour les tâches qui suppriment des données. Une option
--dry-runqui affiche ce qui serait supprimé évite bien des mauvaises surprises.
Conclusion
Le Scheduler apporte une vraie tranquillité : toutes les tâches récurrentes sont décrites dans le code, au même endroit que le reste de l'application, et un seul processus à surveiller sur le serveur. Pour une ou deux tâches simples, une crontab reste parfaitement valable. Mais dès que le nombre de tâches augmente, ou que plusieurs environnements entrent en jeu, le Scheduler devient vite indispensable.
Et vous, quelles tâches planifiées faites-vous tourner dans vos projets ? Partagez-les en commentaire !
Commentaires (0)
Aucun commentaire pour le moment.