Aller au contenu
Efficience IT
·9 min de lecture·Formation

Symfony Messenger vs PHP Enqueue : le verdict en 2026

Par Louis-Arnaud Catoire

Mis à jour le

Symfony Messenger vs PHP Enqueue : le verdict en 2026

En 2022, comparer Symfony Messenger et PHP Enqueue avait du sens. Les deux bibliothèques répondaient au même besoin : traiter des messages de manière asynchrone dans une application PHP. En 2026, la question ne se pose plus. Enqueue n'a reçu aucun commit significatif depuis 2023, ses issues s'accumulent sans réponse, et la compatibilité avec PHP 8.3+ n'est pas garantie. Symfony Messenger, à l'inverse, s'est enrichi à chaque version du framework pour devenir le standard incontesté du messaging asynchrone dans l'écosystème PHP.

Cet article fait le point sur cette évolution et rassemble les bonnes pratiques pour exploiter Messenger dans vos projets en 2026.

L'ascension de Messenger, le déclin d'Enqueue

Messenger est apparu avec Symfony 4.1 en 2018. À ses débuts, il était minimaliste : un bus, quelques transports, une intégration correcte mais sans plus. Pour une vue d'ensemble des bundles disponibles dans l'écosystème Symfony, notre article sur les bundles les plus utilisés dans les projets Symfony situe Messenger dans ce contexte. Enqueue, déjà mature, offrait un catalogue de transports bien plus large (Kafka, Google PubSub, Kinesis, MongoDB, Stomp) et un monitoring intégré. Pour les projets polyglottes ou ceux qui avaient besoin d'un transport exotique, Enqueue était le choix logique.

Depuis, Messenger a rattrapé son retard puis creusé l'écart. Chaque version de Symfony a apporté des améliorations : les stamps pour enrichir les messages de métadonnées, le failure transport pour gérer les échecs, le scheduler pour planifier des tâches récurrentes, le support natif de transports variés. En parallèle, l'écosystème communautaire a produit des transports tiers pour couvrir les besoins spécifiques.

Enqueue, de son côté, a vu son activité décliner progressivement. Le dernier commit significatif sur le repository principal date de 2023. Les pull requests restent ouvertes, les issues sans réponse. La compatibilité avec les versions récentes de PHP et Symfony n'est plus assurée. Pour un projet démarré en 2026, intégrer Enqueue revient à prendre une dette technique immédiate.

Messenger en 2026 : les fondamentaux

Pour ceux qui découvrent Messenger ou qui veulent consolider leurs bases, voici le fonctionnement central du composant.

L'installation tient en une commande, gérée via Composer :

composer require symfony/messenger

La configuration se fait dans messenger.yaml. On y déclare des transports et un routage :

framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
        routing:
            'App\Message\GenererRapportMessage': async

Un message est un simple objet PHP. Un handler le traite :

namespace App\Message;

class GenererRapportMessage
{
    public function __construct(
        private int $rapportId,
        private string $format = 'pdf',
    ) {}

    public function getRapportId(): int
    {
        return $this->rapportId;
    }

    public function getFormat(): string
    {
        return $this->format;
    }
}
namespace App\MessageHandler;

use App\Message\GenererRapportMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
class GenererRapportHandler
{
    public function __invoke(GenererRapportMessage $message): void
    {
    }
}

Le dispatch passe par le MessageBusInterface :

$this->bus->dispatch(new GenererRapportMessage(42, 'xlsx'));

La force de Messenger réside dans sa simplicité conceptuelle. Un POPO comme message, un callable comme handler, un bus comme médiateur. Pas de dépendance vers un protocole de messaging spécifique. Cette séparation nette entre le métier et l'infrastructure est d'ailleurs au cœur de ce qui fait de Symfony un choix pertinent pour les projets d'envergure.

Le pipeline de middlewares, un levier puissant

Ce qui distingue profondément Messenger des alternatives est son pipeline de middlewares. Chaque message passe par une chaîne configurable : validation, transaction Doctrine, logging, ajout de stamps. Ce pipeline est identique en synchrone et en asynchrone, ce qui garantit un comportement cohérent quel que soit le mode d'exécution.

En pratique, vous pouvez injecter un middleware qui wrappe chaque handler dans une transaction Doctrine (doctrine_transaction), un autre qui valide le message avant son traitement, un autre qui mesure les temps d'exécution. L'ordre des middlewares est explicite dans la configuration.

Les stamps constituent un autre mécanisme puissant. Ce sont des métadonnées attachées au message via une enveloppe (Envelope). Les stamps natifs gèrent les délais (DelayStamp), le routage (TransportNamesStamp), la sérialisation. Vous pouvez créer vos propres stamps pour tracer l'origine d'un message, attacher un identifiant de corrélation ou marquer une priorité.

Besoin d'un regard expert sur votre code Symfony ?

Demander un audit gratuit

Choisir son transport : un choix d'architecture

Le transport détermine où et comment les messages sont stockés en attente de traitement. C'est un choix d'architecture plus que de code, au même titre que la décision entre micro-services et monolithe modulaire.

Doctrine : acceptable pour démarrer

Doctrine comme transport est pratique : pas d'infrastructure supplémentaire. Mais sous charge, les limites apparaissent. La table messenger_messages subit des SELECT ... FOR UPDATE qui provoquent des locks. Avec MySQL, des deadlocks surviennent régulièrement sous charge (Deadlock found when trying to get lock; try restarting transaction). La solution de contournement : lancer des workers avec un --time-limit court (60 secondes) et les relancer via crontab pour absorber les crashs. PostgreSQL se comporte mieux grâce au SKIP LOCKED, mais reste limité face à un broker dédié.

RabbitMQ : le choix par défaut

RabbitMQ est le transport recommandé pour la plupart des projets. Il gère nativement les dead letter exchanges, le routing complexe, les priorités de messages et la persistance. Il supporte des dizaines de milliers de messages par seconde. En revanche, il nécessite une maintenance ops (clustering, monitoring Erlang, gestion mémoire).

framework:
    messenger:
        transports:
            async:
                dsn: 'amqp://guest:guest@localhost:5672/%2f/messages'
                options:
                    exchange:
                        name: app_messages
                        type: direct
                    queues:
                        messages:
                            binding_keys: ['app']

Redis Streams : simple et performant

Redis convient aux architectures qui l'utilisent déjà pour le cache. Il est simple à opérer et performant. La contrepartie : moins de garanties de durabilité. Un crash peut perdre des messages si la persistance RDB/AOF n'est pas correctement configurée.

Amazon SQS : zéro ops sur AWS

SQS est le choix naturel sur AWS. Zéro maintenance, scaling automatique, dead letter queues natives. Le compromis : un délai de livraison minimum plus élevé et l'absence de routing avancé.

Kafka via des transports communautaires

Enqueue était souvent choisi pour son support natif de Kafka. Aujourd'hui, des packages communautaires comme koco/messenger-kafka fournissent un transport Kafka pour Messenger. L'intégration est directe et bénéficie du pipeline de middlewares de Messenger. Pour les cas où le volume de données exige une recherche performante, l'intégration d'Elasticsearch avec Symfony complète naturellement Messenger pour indexer les résultats de manière asynchrone.

Stratégies de retry et gestion des échecs

Messenger propose une stratégie de retry déclarative particulièrement bien pensée :

framework:
    messenger:
        failure_transport: failed
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
                    max_delay: 60000
            failed: 'doctrine://default?queue_name=failed'

Après épuisement des retries, le message atterrit dans le failure transport. On peut l'inspecter et le rejouer :

php bin/console messenger:failed:show
php bin/console messenger:failed:retry

Avec Enqueue, la gestion des retries reposait davantage sur les mécanismes natifs du broker (DLX sur RabbitMQ, redrive policy sur SQS). L'approche était fonctionnelle mais plus manuelle et moins intégrée au framework. Le failure transport de Messenger offre un workflow complet directement depuis la console Symfony, sans avoir à manipuler le broker.

Sérialisation : le piège des déploiements

Quand un message traverse un transport, il doit être sérialisé. Messenger utilise par défaut le PhpSerializer, qui repose sur la sérialisation PHP native (serialize()/unserialize()). Le Symfony Serializer (JSON ou XML) peut être configuré explicitement comme alternative. Le piège apparaît lors des déploiements : un message sérialisé avec la version N de votre classe peut être désérialisé par la version N+1, où la structure a changé.

Règle d'or : ne jamais renommer ou supprimer une propriété de message sans avoir vidé la queue au préalable, ou sans avoir mis en place un mécanisme de versioning. Messenger permet de configurer un serializer custom qui gère la rétrocompatibilité, c'est un investissement qui se rentabilise dès le premier incident en production.

Le Scheduler : remplacer vos crons

Depuis Symfony 6.3, le composant Scheduler s'intègre à Messenger pour planifier des tâches récurrentes. Fini le fichier crontab maintenu à la main : chaque tâche est un message dispatché sur une schedule déclarative.

use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;

#[AsSchedule('default')]
class AppScheduleProvider implements ScheduleProviderInterface
{
    public function getSchedule(): Schedule
    {
        return (new Schedule())
            ->add(RecurringMessage::every('1 hour', new NettoyerFichiersTemporaires()))
            ->add(RecurringMessage::cron('0 2 * * *', new GenererRapportQuotidien()));
    }
}

Les tâches bénéficient du même pipeline de middlewares, du même système de retry et du même monitoring que n'importe quel message. C'est une unification élégante qui réduit la surface de maintenance.

Idempotence et at-least-once

Messenger fonctionne en at-least-once : un message sera traité au moins une fois, potentiellement plus en cas de crash du worker entre le traitement et l'acknowledgement. Aucun système de messaging distribué ne garantit un traitement exactly-once de manière fiable.

Chaque handler doit donc être idempotent : traiter deux fois le même message ne doit pas corrompre l'état du système. Concrètement, cela signifie utiliser des clés d'idempotence, vérifier l'état avant d'agir, et préférer les opérations UPSERT aux INSERT.

Scaling des workers en production

Pour absorber un pic de charge, on ajoute des workers. Avec Doctrine comme transport, multiplier les workers provoque des contentions. Avec RabbitMQ ou SQS, le scaling horizontal est linéaire : chaque worker supplémentaire consomme sa part de messages sans conflit.

Supervisez vos workers avec supervisor ou systemd, idéalement dans des conteneurs Docker. Configurez un --memory-limit et un --time-limit pour forcer le recyclage régulier des processus et éviter les fuites mémoire :

php bin/console messenger:consume async --memory-limit=128M --time-limit=3600

Messenger comme fondation CQRS

Messenger ne se limite pas à l'async. En déclarant plusieurs bus (command bus, query bus, event bus), il devient la colonne vertébrale d'une architecture CQRS. Les commands modifient l'état, les queries le lisent, les events propagent les effets de bord. Ce pattern découple votre domaine métier de l'infrastructure et facilite la testabilité.

Enqueue ne proposait pas cette abstraction. C'était un outil de queuing, pas un bus applicatif. Si votre ambition est de structurer votre architecture autour du messaging, Messenger est le choix naturel et le seul activement maintenu.

Migrer depuis Enqueue

Si vous maintenez un projet qui utilise encore Enqueue, la migration vers Messenger est un investissement raisonnable. Les concepts se transposent : les Producer deviennent des appels à MessageBusInterface::dispatch(), les Processor deviennent des handlers marqués #[AsMessageHandler], les topics et queues se mappent sur des transports Messenger.

Le point d'attention principal est la coexistence pendant la migration. Si vos queues contiennent des messages au format Enqueue, vous devrez soit les vider avant de basculer, soit implémenter un serializer de transition qui comprend les deux formats. Privilégiez une migration progressive transport par transport plutôt qu'un big bang. La gestion des dépendances via Composer est aussi un point d'attention : Enqueue et Messenger peuvent coexister temporairement dans le composer.json pendant la transition.

Tableau comparatif Messenger vs Enqueue

CritèreSymfony MessengerPHP Enqueue
MaintenanceActive (chaque version Symfony)Quasi abandonné depuis 2023
Intégration SymfonyNative (autowiring, DI, Profiler)Via bridge, moins intégré
Pipeline de middlewaresOui (validation, transaction, stamps)Non
Transports natifsDoctrine, AMQP, Redis, SQS, In-MemoryAMQP, Kafka, Redis, SQS, GPS, MongoDB
Transports communautairesKafka, Beanstalkd et autresLimités (maintenance inactive)
Failure transportNatif (inspection et replay en CLI)Via mécanismes du broker
SchedulerIntégré depuis Symfony 6.3Non
Multi-bus (CQRS)Oui (command, query, event)Non
Serialisation configurableOui (serializer custom)Basique
Compatibilité PHP 8.3+OuiNon garantie

Verdict 2026

En 2026, le choix est clair : Symfony Messenger est le standard. Il couvre tous les cas d'usage du messaging asynchrone dans l'écosystème Symfony, il évolue avec chaque version du framework, et sa communauté est massive.

Enqueue a rendu de grands services à l'écosystème PHP. Il a démocratisé le messaging asynchrone et a poussé Messenger à s'améliorer. Mais un projet sans maintenance active depuis trois ans ne peut plus être recommandé pour de nouveaux développements.

Investissez dans un broker dédié dès que le volume dépasse quelques milliers de messages par jour, rendez vos handlers idempotents, exploitez le Scheduler pour vos tâches récurrentes, et traitez la sérialisation comme un contrat d'API.

Pour aller plus loin

Si vous souhaitez aller plus loin, nous mettons à disposition des développeurs spécialisés sur Symfony qui sauront intégrer Messenger dans vos projets. N'hésitez pas à nous contacter, ou à approfondir la documentation officielle de Messenger.

Besoin d'expertise Symfony ?

Architecture, dette technique, migration ou performance : notre équipe accompagne les projets Symfony exigeants depuis 2018.

Demander un audit gratuit

Articles connexes