Image de couverture : Symfony est un adaptateur, pas votre application — pourquoi cette distinction change tout
tech

Symfony est un adaptateur, pas votre application — pourquoi cette distinction change tout

20 May 2026
6 min de lecture
10 vues
Sébastien Muler

Symfony est un adaptateur, pas votre application

Cet article s'inspire de Symfony Is Also an Adapter de Gabriel Anhaia, que nous enrichissons ici d'une perspective orientée investissement logiciel.

Une base de code Symfony vieille de quatre ans donne souvent une bonne impression à distance : les contrôleurs sont fins, les repositories existent en tant que classes, le container est câblé avec des attributs. L'équipe semble avoir fait de l'architecture.

Mais ouvrez l'entité. Vous trouvez un #[PrePersist] qui calcule une TVA. Le repository étend ServiceEntityRepository et retourne des entités vivantes avec des collections Doctrine toujours attachées. La couche « service » est une chaîne de trois classes (OrderServiceOrderManagerOrderProcessor) dont une méthode privée décide si un remboursement est légal.

Ce n'est pas faux selon les standards Symfony. C'est simplement couplé au framework de bout en bout. Et c'est là que le coût s'accumule.


Le vrai problème : confondre le framework et l'application

Symfony est excellent pour ce qu'il fait : router des requêtes HTTP, injecter des dépendances, parler à une base de données, envoyer des emails. Ce sont des préoccupations d'infrastructure.

Votre domaine métier — les règles de remboursement, le calcul de prix, la validation d'un statut de commande — n'a rien à voir avec HTTP ou Doctrine. Pourtant, dans la majorité des codebases Symfony rencontrées en production, ces deux mondes sont mélangés.

Le résultat concret :

  • Les tests sont lents parce qu'ils chargent le kernel Symfony pour tester une règle de gestion.
  • Les migrations de version sont douloureuses parce que le domaine utilise des annotations ou des classes Doctrine dépréciées.
  • L'onboarding est difficile parce que la logique métier est éparpillée entre entités, listeners, services et commandes.

Traiter Symfony comme l'application — plutôt que comme un adaptateur qui branche votre application sur le monde extérieur — est un choix d'architecture qui se paie cash lors de chaque montée de version majeure.


L'architecture hexagonale comme réponse structurelle

L'architecture hexagonale (ou Ports & Adapters) répond précisément à ce problème. Le principe : votre domaine est au centre, et tout le reste s'y branche via des interfaces.

Concrètement, votre arborescence cible ressemble à :

src/
├── Domain/
│   ├── Model/          # Entités pures, sans annotation Doctrine
│   ├── Repository/     # Interfaces seulement
│   └── Service/        # Logique métier pure
├── Application/
│   └── UseCase/        # Orchestration des flux
└── Infrastructure/
    ├── Persistence/    # Implémentations Doctrine des repositories
    ├── Http/           # Contrôleurs Symfony
    └── Messaging/      # Consumers, mailers, etc.

Le domaine n'a aucune idée que Symfony existe

Une Order dans Domain/Model/Order.php est une classe PHP pure. Pas d'annotation #[ORM\Entity], pas de #[PrePersist]. La règle de remboursement vit dans Domain/Service/RefundPolicy.php et prend des value objects en paramètre — pas une Request Symfony.

// Domain/Service/RefundPolicy.php
final class RefundPolicy
{
    public function isEligible(Order $order, \DateTimeImmutable $requestedAt): bool
    {
        return $order->isPaid()
            && $requestedAt <= $order->paidAt()->modify('+30 days');
    }
}

Cette classe se teste en une milliseconde, sans kernel, sans base de données.

Symfony devient un adaptateur HTTP

Le contrôleur Symfony fait une seule chose : traduire la requête HTTP en appel de use case, puis traduire la réponse du use case en réponse HTTP.

// Infrastructure/Http/RefundController.php
final class RefundController extends AbstractController
{
    public function __construct(
        private readonly RequestRefund $useCase
    ) {}

    #[Route('/orders/{id}/refund', methods: ['POST'])]
    public function __invoke(string $id, Request $request): JsonResponse
    {
        $this->useCase->execute(new RequestRefundCommand($id));
        return $this->json(['status' => 'refund_requested'], 202);
    }
}

Doctrine devient un adaptateur de persistance

Votre repository Doctrine implémente l'interface définie dans le domaine. Le domaine ne sait pas que Doctrine existe — il sait seulement qu'il peut appeler OrderRepository::findById().

// Infrastructure/Persistence/DoctrineOrderRepository.php
final class DoctrineOrderRepository implements OrderRepository
{
    public function findById(OrderId $id): ?Order
    {
        // mapping depuis l'entité Doctrine vers le modèle domaine
    }
}

Ce que ça change concrètement pour votre investissement logiciel

Refactorer vers cette structure demande du temps. La question n'est pas « est-ce utile ? » mais « à quel moment ce coût est-il inférieur au coût de ne pas le faire ? »

Voici les signaux concrets qui indiquent que le moment est venu :

  • Les tests d'intégration sont votre seul filet de sécurité : vous ne pouvez plus tester la logique métier sans démarrer le kernel.
  • Chaque mise à jour Symfony nécessite de toucher le domaine : les annotations changent, les classes de base sont dépréciées, et vos entités sont en première ligne.
  • L'ajout d'une nouvelle interface (API REST, CLI, queue) nécessite de dupliquer de la logique parce qu'elle est enfouie dans des contrôleurs ou des listeners.
  • Le turnover coûte cher : un nouveau développeur met plusieurs semaines à comprendre où vit la logique métier.

Inversement, une application bien découplée permet de :

✅ Migrer de Doctrine 2 à Doctrine 3 en touchant uniquement Infrastructure/Persistence/
✅ Ajouter un consumer de queue qui réutilise le même use case que le contrôleur HTTP
✅ Atteindre 80% de couverture de tests unitaires rapides, sans mock du container
✅ Onboarder un développeur junior sur le domaine métier sans qu'il touche à Symfony


Conclusion : Symfony est un excellent outil — à condition de savoir où il s'arrête

Le problème n'est pas Symfony. Symfony est un framework remarquable, mature, et bien maintenu. Le problème est de le laisser dicter la forme de votre domaine métier.

Traiter Symfony — comme Laravel, comme n'importe quel framework — comme un adaptateur branché sur votre application, et non comme l'application elle-même, est la décision d'architecture qui détermine si votre codebase sera maintenable dans cinq ans.

Ce n'est pas une question de perfection architecturale. C'est une question d'investissement : chaque ligne de code métier indépendante du framework est une ligne que vous n'aurez pas à réécrire lors de la prochaine mise à jour majeure.

Chez MulerTech, nous accompagnons les équipes dans cette transition — que ce soit par un audit de codebase, une formation à l'architecture hexagonale appliquée à Symfony, ou un accompagnement sur la durée. Contactez-nous pour en discuter.

Partager cet article