Aller au contenu principal
Retour au blog

Symfony 7.1 : TypeInfo, MapRouteParameter et 2 attributs DI

Flavien Métivier11 juin 20245 min

Symfony 7.1.0 est sorti la semaine du 27 mai 2024, documenté par pas moins de 48 articles officiels « New in Symfony 7.1 » sur blog.symfony.com. Pas une release majeure en apparence — pas de rupture, pas de grand chamboulement — mais trois grandes nouveautés changent vraiment la façon d'écrire des controllers et des services au quotidien : le composant TypeInfo, les Mapped Route Parameters, et deux nouveaux attributs d'injection de dépendances. Tour d'horizon rapide de ce qui mérite ton attention.

TypeInfo Component : l'introspection des types PHP enfin centralisée

Jusqu'ici, plusieurs composants Symfony (Serializer, Validator, PropertyInfo) avaient chacun leur propre mécanique pour inspecter les types PHP. Résultat : des comportements légèrement divergents selon le contexte, et des bundles tiers qui réimplémentaient souvent la même logique. Le composant TypeInfo unifie tout ça. Il expose un modèle objet cohérent pour représenter n'importe quel type PHP 8.x : scalaires, unions, intersections, nullable, collections, et même les génériques exprimés via PHPDoc.

use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
use Symfony\Component\TypeInfo\Type\CollectionType;

$resolver = TypeResolver::createFromReflection();

// Introspection d'une propriété PHP 8.3
$type = $resolver->resolve(new \ReflectionProperty(Product::class, 'tags'));
// => CollectionType<string>

var_dump($type->isNullable());   // bool(false)
var_dump($type->getBaseType());  // string("array")

// Fonctionne aussi sur les paramètres de méthode
$paramType = $resolver->resolve(
    (new \ReflectionMethod(OrderService::class, 'create'))
        ->getParameters()[0]
);

Le bénéfice direct pour le code applicatif est modeste à court terme. En revanche, pour l'écosystème — API Platform 3.x, EasyAdmin, les sérialiseurs custom — c'est structurant : les bundles vont converger vers cette API commune plutôt que de maintenir chacun leur propre couche d'introspection. À la clé : moins de bugs silencieux liés à des interprétations divergentes des types union ou des promoted properties.

Mapped Route Parameters : finis les #[MapEntity] partout

Symfony 6.2 avait introduit #[MapEntity] pour hydrater une entité Doctrine directement depuis le paramètre de route. Utile, mais couplé à Doctrine et verbeux dès qu'on manipule des UUID, des enums, ou des types scalaires qui n'ont rien à voir avec une entité. Symfony 7.1 introduit #[MapRouteParameter], plus générique : il caste automatiquement le paramètre de route vers le type déclaré dans la signature du controller, via les value resolvers existants.

use Symfony\Component\HttpKernel\Attribute\MapRouteParameter;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\HttpFoundation\Response;

#[Route('/orders/{orderId}/lines/{lineId}')]
public function showLine(
    #[MapRouteParameter] Uuid $orderId,
    #[MapRouteParameter] int $lineId,
): Response {
    // $orderId est déjà un objet Uuid
    // $lineId est déjà un int — plus de (int) $request->attributes->get('lineId')
    // Aucune magie Doctrine requise
}

L'attribut accepte un argument name quand le paramètre de route et l'argument PHP ne portent pas le même nom — cas fréquent quand tu travailles avec des conventions snake_case en URL et camelCase en PHP :

#[Route('/articles/{article_slug}')]
public function show(
    #[MapRouteParameter(name: 'article_slug')] string $slug,
): Response {
    // ...
}

Pour les entités Doctrine, #[MapEntity] reste le bon outil. Pour tout le reste — UUID, ULID, enums, entiers — #[MapRouteParameter] est la solution la plus propre. Les controllers gagnent en lisibilité, et les tests unitaires deviennent plus directs puisque tu passes directement des types PHP bien cadrés à tes méthodes.

Besoin d'un expert Symfony ?

Réserver un appel

Nouveaux attributs DI : AutowireInline et AutowireMethodOf

#[AutowireInline] : déclarer un service sans passer par services.yaml

#[AutowireInline] permet de définir un service « inline » directement dans l'attribut PHP, sans entrée dans services.yaml. Idéal pour les adaptateurs légers ou les value objects qui n'ont pas vocation à être réutilisés dans plusieurs services — tu gardes la configuration au plus près du code qui en a besoin.

use Symfony\Component\DependencyInjection\Attribute\AutowireInline;

class ReportGenerator
{
    public function __construct(
        #[AutowireInline(
            class: CsvFormatter::class,
            arguments: ['%kernel.project_dir%/var/exports'],
        )]
        private readonly CsvFormatter $formatter,
    ) {}

    public function generate(array $rows): string
    {
        return $this->formatter->format($rows);
    }
}

#[AutowireMethodOf] : injecter une méthode comme callable

#[AutowireMethodOf] injecte une méthode d'un service existant sous forme de \Closure. Plus propre qu'injecter le service entier juste pour appeler une méthode, et nettement meilleur pour la testabilité — tu peux substituer n'importe quel callable dans le constructeur lors des tests unitaires, sans mocker l'ensemble du service.

use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;

class NotificationSender
{
    public function __construct(
        // Injecte PriceCalculator::computeVat() comme Closure
        #[AutowireMethodOf(service: PriceCalculator::class, method: 'computeVat')]
        private readonly \Closure $vatComputer,
    ) {}

    public function send(Order $order): void
    {
        $vat = ($this->vatComputer)($order->getSubtotal(), $order->getCountry());
        // ... envoi de la notification avec le montant TTC
    }
}

// En test unitaire, injection directe d'un callable :
$sender = new NotificationSender(
    fn (float $amount, string $country) => $amount * 0.2,
);

Ce qu'il faut retenir

  • TypeInfo centralise l'introspection des types pour tout l'écosystème — impact fort sur les bundles, impact modéré pour le code applicatif aujourd'hui.
  • #[MapRouteParameter] remplace avantageusement #[MapEntity] pour les types non-Doctrine : UUID, ULID, enums, entiers.
  • #[AutowireInline] réduit le YAML de configuration pour les services locaux à un seul consommateur.
  • #[AutowireMethodOf] améliore la testabilité en évitant d'injecter un service entier pour une seule méthode.
  • Symfony 7.1 requiert PHP 8.2 minimum (PHP 8.3 recommandé). La montée depuis 7.0 est non-breaking : un composer update 'symfony/*' suffit dans la grande majorité des cas.

Symfony 7.1 est une release d'ergonomie et de cohérence. Pas de grand saut architectural, mais des gains concrets dans l'écriture quotidienne des controllers et des services — exactement le type de mise à jour qu'on a intérêt à embarquer vite pour garder la codebase lisible. Si tu veux évaluer l'impact sur ta stack ou sécuriser la montée de version, le Bear Upgrade est fait pour ça : audit de compatibilité, plan de migration priorisé, et accompagnement jusqu'en prod.

Cet article vous a plu ? Partagez-le !

Besoin d'un expert Symfony ?

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