Symfony11 min

Bonnes Pratiques Symfony en 2026 : Ce qui a Change

Par Pierre-Arthur Demengel
SymfonyPHPArchitectureBonnes pratiques

Symfony evolue constamment. Certaines pratiques considerees comme standard il y a 2 ans sont aujourd'hui obsoletes. Voici un guide actualise des meilleures pratiques pour vos projets Symfony en 2026.

1. Architecture : Hexagonale par defaut

L'architecture hexagonale (ports & adapters) est devenue le standard pour les projets Symfony non-triviaux :

src/
  Domain/          # Entites, Value Objects, Interfaces (ports)
  Application/     # Use cases, Command/Query handlers
  Infrastructure/  # Implementations (Doctrine, API, Mailer...)
  Ui/              # Controllers, CLI commands

Les avantages sont clairs : testabilité, independance du framework, et lisibilité du code métier.

Quand NE PAS l'utiliser

Pour un CRUD simple ou un PoC, le classique Controller + Entity + Repository reste pertinent. N'ajoutez pas de complexité inutile.

2. Injection de dependances : autowiring + attributes

Depuis Symfony 6.4+, privilegiez les PHP attributes pour la configuration des services :

use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\When;

#[When(env: 'prod')]
class CachedProductRepository implements ProductRepositoryInterface
{
    public function __construct(
        #[Autowire(service: 'app.repository.product.inner')]
        private ProductRepositoryInterface $inner,
        private CacheInterface $cache,
    ) {}
}

Évitez les services.yaml massifs

Avec l'autowiring et les attributes, votre services.yaml ne devrait contenir que les cas exceptionnels (bindings globaux, parametres). Si vous avez plus de 20 lignes de configuration manuelle, c'est un code smell.

3. Messenger : l'epine dorsale asynchrone

Symfony Messenger n'est plus optionnel — c'est le pattern standard pour :

  • Envoi d'emails
  • Traitement d'images
  • Synchronisation avec des services tiers
  • Tout ce qui peut attendre > 100ms
#[AsMessageHandler]
class SendWelcomeEmailHandler
{
    public function __construct(
        private MailerInterface $mailer,
        private Environment $twig,
    ) {}

    public function __invoke(UserRegistered $event): void
    {
        $html = $this->twig->render('emails/welcome.html.twig', [
            'user' => $event->user,
        ]);

        $email = (new Email())
            ->to($event->user->getEmail())
            ->subject('Bienvenue !')
            ->html($html);

        $this->mailer->send($email);
    }
}

Transports recommandes

  • Doctrine : simple, pas d'infra supplementaire (petits projets)
  • Redis : performant, adapte au temps reel
  • RabbitMQ/Amazon SQS : haute disponibilité, projets critiques

4. Sécurité : les nouveautes a adopter

Access Token Authenticator

Depuis Symfony 6.4, utilisez le nouvel authenticator natif pour les APIs :

# config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            access_token:
                token_handler: App\Security\ApiTokenHandler

Voter : le seul pattern d'autorisation

Arretez d'utiliser is_granted('ROLE_ADMIN') pour la logique métier. Les Voters encapsulent la logique d'autorisation :

#[IsGranted(new Expression('is_granted("EDIT", subject)'), subject: 'product')]
public function edit(Product $product): Response { ... }

5. Doctrine : les optimisations qui comptent

Read Models

Pour les requetes complexes, utilisez des DTOs plutot que des entites hydratees :

$results = $this->em->createQuery('
    SELECT NEW App\Dto\ProductListItem(p.id, p.name, p.price, c.name)
    FROM App\Entity\Product p
    JOIN p.category c
    WHERE p.active = true
')->getResult();

Batch processing

$batchSize = 100;
$i = 0;
foreach ($query->toIterable() as $entity) {
    // process
    if ((++$i % $batchSize) === 0) {
        $this->em->flush();
        $this->em->clear();
    }
}
$this->em->flush();

6. Tests : la pyramide pragmatique

  • Unit tests : Domain layer (value objects, services purs)
  • Intégration tests : Repositories, handlers Messenger
  • Functional tests : Endpoints API (WebTestCase)
  • E2E : parcours critiques uniquement (Playwright/Panther)
// Test fonctionnel API avec le nouveau approche
class ProductApiTest extends ApiTestCase
{
    public function testListProducts(): void
    {
        $response = static::createClient()->request('GET', '/api/products');

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains(['hydra:totalItems' => 10]);
    }
}

7. Performance : les quick wins

  • Preload : activez opcache.preload avec le fichier génère par Composer
  • HTTP Cache : utilisez les headers Cache-Control et ETag nativement
  • Lazy services : marquez les services couteux en lazy via l'attribut #[Lazy]
  • AssetMapper : remplacez Webpack Encore pour les projets sans build JS complexe

8. Déploiement : les standards actuels

  • Docker avec FrankenPHP (worker mode) pour les performances
  • CI/CD : GitHub Actions avec cache Composer/PHPStan
  • Analyse statique : PHPStan level 8 minimum, Rector pour les mises a jour auto

Conclusion

Les bonnes pratiques Symfony en 2026 privilegient la simplicite (autowiring, attributes), l'asynchrone (Messenger), la séparation des responsabilites (hexagonale) et les outils modernes (AssetMapper, FrankenPHP). Adoptez-les progressivement — chaque refactoring est une opportunite d'amelioration.

13 projets livresGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tete ?

Discutons de votre site web. Réponse garantie sous 24h.

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler