Aller au contenu principal
Retour au blog

Symfony 7.2 : #[AsMessage], #[WhenNot] et l'ExpressionLanguage

Flavien Métivier3 décembre 20244 min

Symfony 7.2.0 est sorti le 29 novembre 2024. Les articles de veille se sont naturellement concentrés sur le CSRF stateless et la stabilisation de TypeInfo — deux ajouts de premier plan. Dans leur sillage, trois fonctionnalités plus discrètes sont passées sous le radar, alors qu'elles changent concrètement l'ergonomie du code au quotidien. #[AsMessage] colle enfin le routing Messenger à la classe de message elle-même. #[WhenNot] complète la logique conditionnelle du conteneur DI avec l'opérateur inverse qui manquait. L'ExpressionLanguage gagne des opérateurs null-safe et de coalescence directement inspirés du PHP moderne. Tour rapide — avec du code que tu peux activer dès aujourd'hui.

#[AsMessage] — le transport Messenger enfin colocalisé

Avant 7.2, déclarer le transport d'un message Messenger impliquait un aller-retour obligatoire vers config/packages/messenger.yaml. La classe de message restait muette sur sa propre destination : le routing vivait ailleurs, dans un fichier que les nouveaux venus sur le projet cherchaient pendant dix minutes. Le nouvel attribut #[AsMessage] résout ce problème structurel — la classe porte désormais elle-même l'information de transport.

<?php
// src/Message/SendWelcomeEmail.php

use Symfony\Component\Messenger\Attribute\AsMessage;

#[AsMessage(transport: 'async')]
final class SendWelcomeEmail
{
    public function __construct(
        public readonly string $email,
        public readonly string $name,
    ) {}
}

// Dispatch identique — rien ne change côté handler
$messageBus->dispatch(new SendWelcomeEmail(
    email: 'user@example.com',
    name: 'Alice',
));

Le routing déclaré via #[AsMessage] prend le dessus sur une éventuelle entrée dupliquée dans le YAML, ce qui permet une migration progressive sans risque de régression. Pour les messages qui doivent transiter par plusieurs canaux simultanément, il suffit de passer un tableau : #[AsMessage(transport: ['async', 'audit_log'])]. Résultat : la cohérence namespace/transport devient visible d'un simple grep sur le répertoire Message/, sans ouvrir aucun fichier de configuration.

#[WhenNot] — l'inverse conditionnel que le conteneur attendait

#[When] existait déjà pour conditionner l'enregistrement d'un service à un environnement donné. Sa limitation : il était impossible d'exprimer « partout sauf en test » sans énumérer explicitement tous les environnements existants. #[WhenNot] ferme ce trou en une seule annotation. L'usage le plus immédiat concerne les adaptateurs d'infrastructure que l'on veut exclure en environnement de test pour les remplacer par des doublures légères.

<?php
// src/Payment/StripeGateway.php

use Symfony\Component\DependencyInjection\Attribute\WhenNot;

#[WhenNot(env: 'test')]
class StripeGateway implements PaymentGateway
{
    public function __construct(
        private readonly string $apiKey,
    ) {}

    public function charge(int $amountCents, string $currency): ChargeResult
    {
        // appel réel à l'API Stripe
    }
}

// src/Payment/FakeGateway.php — enregistré uniquement en test
#[When(env: 'test')]
class FakeGateway implements PaymentGateway
{
    public function charge(int $amountCents, string $currency): ChargeResult
    {
        return ChargeResult::success(transactionId: 'fake_'.uniqid());
    }
}
  • #[WhenNot('test')] : service actif dans dev, prod, staging — inactif uniquement en test.
  • #[When('test')] sur la doublure : symétrie propre, zéro configuration YAML.
  • Combinable avec autowire et autoconfigure — aucun changement de services.yaml requis.
  • Fonctionne aussi sur les méthodes de fabrique et les décorateurs.

Besoin d'un expert Symfony ?

Réserver un appel

ExpressionLanguage — null-safe, coalescence et puissance

Symfony 7.2 intègre dans le composant ExpressionLanguage trois opérateurs qui rapprochent sa syntaxe du PHP moderne. L'opérateur null-safe ?. court-circuite la chaîne dès qu'un maillon vaut null. L'opérateur de coalescence ?? fournit une valeur de repli sans condition explicite. Et l'opérateur puissance ** évite les appels à pow() dans les expressions de règles métier. Ces trois additions sont particulièrement pertinentes si tu utilises le composant dans un moteur de règles ou dans la configuration de workflows complexes.

<?php

use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$el = new ExpressionLanguage();

// Opérateur null-safe : pas d'exception si $user->profile est null
$avatar = $el->evaluate(
    'user?.profile?.avatarUrl',
    ['user' => $user]
);
// Retourne null proprement si profile === null

// Coalescence : valeur par défaut sans condition explicite
$timeout = $el->evaluate(
    'config["timeout"] ?? 30',
    ['config' => $appConfig]
);

// Opérateur puissance : lisible dans les règles de scoring
$score = $el->evaluate(
    'base ** exponent',
    ['base' => 2, 'exponent' => 10]
);
// Retourne 1024

Ces opérateurs s'inscrivent dans un effort continu pour que l'ExpressionLanguage reste utilisable sans recourir à des fonctions custom à chaque besoin légèrement avancé. Dans un contexte de moteur de règles — éligibilité, scoring, workflow conditionnel — le ?. élimine des couches entières de guards défensifs qui alourdissaient les expressions de configuration. Le code d'expression gagne en lisibilité ce qu'il perdait en verbosité.

Ce qu'il faut retenir et activer maintenant

  • Migrer le routing Messenger progressivement : ajoute #[AsMessage] classe par classe, supprime les entrées YAML correspondantes au fur et à mesure.
  • Auditer les stubs de test : remplace les conditions d'environnement dans services.yaml par la paire #[WhenNot] / #[When] pour un code auto-documenté.
  • Réviser les expressions complexes : partout où tu empiles des null-guards dans tes expressions, le ?. réduit le bruit de moitié.
  • Symfony 7.2 exige PHP 8.2 minimum — les attributs natifs et l'opérateur null-safe côté PHP sont donc déjà garantis dans l'environnement cible.

Ces trois fonctionnalités ne font pas les grands titres, mais elles réduisent concrètement la distance entre l'intention du développeur et le code qu'il écrit. #[AsMessage] et #[WhenNot] éliminent des sauts de contexte entre classe et configuration ; les nouveaux opérateurs de l'ExpressionLanguage rapprochent sa syntaxe du PHP que tout le monde lit déjà. La mise à jour depuis Symfony 7.1 est sans breaking change notable sur ces points — aucune raison de procrastiner. Si tu veux faire le point sur l'état de ton application Symfony avant ou après la migration vers la 7.2, le Bear Scan (audit technique externalisé) est là pour ça — thebeardedcto.com/services.

Cet article vous a plu ? Partagez-le !

Besoin d'un expert Symfony ?

20 ans d'expérience sur l'écosystème PHP/Symfony.