Bonnes Pratiques Symfony en 2026 : Ce qui a Change
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.preloadavec le fichier génère par Composer - HTTP Cache : utilisez les headers
Cache-ControletETagnativement - 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.
