Qu'est-ce que Symfony ?
Symfony est un framework PHP open-source destiné au développement web moderne. Il permet de créer des applications web performantes grâce à un ensemble de composants réutilisables et une architecture MVC modulaire. Symfony est reconnu pour sa robustesse, sa flexibilité et son écosystème riche comprenant des centaines de packages et bundles. Il est utilisé aussi bien pour des CMS que pour des architectures de plateformes à grande échelle.
Les nouveautés et améliorations de la version 8 de Symfony
Avec la version 8, Symfony intègre en profondeur les dernières avancées de PHP 8.4 et supprime toutes les dépréciations liées à la version 7.0. Cette mise à jour se concentre sur deux axes majeurs : enrichir l'expérience des développeurs et pousser les performances des applications à un niveau supérieur.
Exigence de PHP 8.4 minimum
La plus grosse nouveauté de Symfony 8 est son exigence de PHP 8.4 minimum pour fonctionner. Ce choix stratégique a été fait pour répondre à plusieurs objectifs :
Sécurité : Symfony veut s'assurer de fonctionner sur des versions de PHP les plus récentes, abandonnant ainsi les anciennes versions qui ne reçoivent plus de correctifs de sécurité.
Performances : Grâce à PHP 8.4, Symfony peut exploiter les dernières fonctionnalités liées aux gains de performance, comme les Lazy Objects qui permettent d'initialiser les services uniquement lorsqu'ils sont réellement utilisés.
<?php
use Symfony\Component\DependencyInjection\Attribute\Lazy;
#[Lazy]
class ExpensiveService
{
private DatabaseConnection $connection;
private ComplexCalculator $calculator;
public function __construct(
DatabaseConnection $connection,
ComplexCalculator $calculator
) {
// Initialisé uniquement quand le service est réellement utilisé
}
}
Les commandes invocables (Invokable Commands)
L'une des fonctionnalités les plus attendues de Symfony 8 est la stabilisation des commandes invocables. Introduites dans Symfony 7.3 et enrichies dans la version 7.4, elles permettent de simplifier drastiquement l'écriture des commandes console.
Avant (approche classique)
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand extends Command
{
protected function configure(): void
{
$this->addArgument('name', InputArgument::REQUIRED);
$this->addOption('activate', null, InputOption::VALUE_NONE);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$activate = $input->getOption('activate');
// ...
return Command::SUCCESS;
}
}
Après (approche invocable)
<?php
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
public function __invoke(
SymfonyStyle $io,
#[Argument] string $name,
#[Option] bool $activate = false,
): int {
// $name et $activate sont directement disponibles
return Command::SUCCESS;
}
}
Les principaux avantages sont :
- Plus besoin d'hériter de la classe
Command
- Plus besoin de surcharger la méthode
configure()
- Les valeurs des arguments et options sont directement disponibles comme variables
Support des enums et DTOs
Symfony 8 améliore encore cette fonctionnalité avec le support des backed enums et l'attribut #[MapInput] pour les DTOs :
<?php
enum CloudRegion: string
{
case East = 'east';
case West = 'west';
}
class AddServerInput
{
#[Argument]
public CloudRegion $region;
#[Option]
public ?int $size = null;
}
#[AsCommand('app:add-server')]
class AddServerCommand
{
public function __invoke(
OutputInterface $output,
#[MapInput] AddServerInput $server,
): int {
// Utilisation de $server->region et $server->size
return Command::SUCCESS;
}
}
Nouveaux composants stabilisés
Symfony 8 améliore plusieurs composants qui étaient expérimentaux dans la version 7.x :
TypeInfo
Le composant TypeInfo extrait les informations de type PHP depuis les propriétés, arguments et types de retour. Il gère les unions, intersections et génériques.
<?php
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
class Dummy
{
public function __construct(
public int $id,
/** @var string[] $tags */
public array $tags,
) {
}
}
$typeResolver = TypeResolver::create();
$typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id'));
// Retourne un type "int"
ObjectMapper
Le composant ObjectMapper simplifie la transformation d'objets, particulièrement utile pour mapper des DTOs vers des entités. Dans l'exemple ci-dessous, on définit un ProductInputDto qui sera automatiquement converti en entité Product grâce aux attributs #[Map] :
<?php
use App\Entity\Product;
use Symfony\Component\ObjectMapper\Attribute\Map;
#[Map(target: Product::class)]
class ProductInputDto
{
#[Map(target: 'name')]
public string $productName;
public string $description;
#[Map(if: false)]
public string $internalSku = '';
#[Map(transform: 'floatval')]
public string $price;
}
Utilisation dans un contrôleur :
<?php
#[Route('/product', name: 'product_create', methods: ['POST'])]
public function create(
#[MapRequestPayload] ProductInputDto $productInput,
ObjectMapperInterface $objectMapper,
EntityManagerInterface $entityManager
): Response {
$product = $objectMapper->map($productInput);
$entityManager->persist($product);
$entityManager->flush();
return $this->json(['message' => 'Produit créé']);
}
JsonPath
Le composant JsonPath permet d'interroger et d'extraire des données JSON en utilisant des expressions, similaire à DomCrawler pour le HTML :
<?php
use Symfony\Component\JsonPath\JsonCrawler;
$json = '{"store": {"books": [{"title": "Symfony Guide", "price": 29.99}]}}';
$crawler = new JsonCrawler($json);
$titles = $crawler->find('$.store.books[*].title');
// Filtre avancé
$cheapBooks = $crawler->find('$.store.books[?(@.price < 30)]');
JsonStreamer
Pour les APIs manipulant de gros volumes JSON, JsonStreamer offre un encodeur/décodeur streaming environ 10× plus rapide et 50-90% plus léger en mémoire que le Serializer :
<?php
use Symfony\Component\JsonStreamer\JsonStreamEncoder;
$encoder = new JsonStreamEncoder();
foreach ($encoder->encode($largeDataset) as $chunk) {
echo $chunk;
}
Nouvelle configuration PHP avec tableaux
Symfony 8 abandonne les configurations XML et PHP fluent au profit des tableaux PHP. Un format compact et expressif avec autocomplétion et validation de types :
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'pattern' => '^/*',
'lazy' => true,
'anonymous' => true,
],
],
'access_control' => [
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
],
]
]);
Pour ceux qui ne sont pas prêts, YAML reste disponible.
Gestion améliorée des dates sans fuseau horaire
Symfony 8 introduit un nouveau système pour gérer les dates "flottantes" — ces dates qui n'ont pas de fuseau horaire associé, comme une date de naissance ou une alarme quotidienne.
Le problème
PHP associait automatiquement un fuseau horaire à toutes les dates, ce qui créait des décalages inattendus. Une date de naissance comme "15 mai 1990" pouvait se transformer en "14 mai 1990 à 23h" selon le fuseau du serveur.
La solution
Symfony propose désormais trois types de points temporels :
<?php
use Symfony\Component\Form\Extension\Core\Type\DateType;
// Pour une date (ex : 2025-10-29)
$builder->add('birthdate', DateType::class, [
'input' => 'date_point', // Utilise DayPoint
]);
// Pour une heure
$builder->add('alarm', TimeType::class, [
'input' => 'date_point', // Utilise TimePoint
]);
UuidV7 par défaut
Symfony 8 utilise maintenant UuidV7 par défaut pour les identifiants uniques. Cette version est plus précise, capable de gérer le temps à la microseconde tout en respectant la RFC 9562.
Pour les tests, un nouvel outil MockUuidFactory permet de fixer les UUID générés :
<?php
use Symfony\Component\Uid\Factory\MockUuidFactory;
$factory = new MockUuidFactory('01912d08-2000-7000-8000-000000000001');
// Tous les UUID générés seront prévisibles pendant les tests
Améliorations des performances
Symfony 8 apporte des optimisations significatives :
- Lazy Objects PHP 8.4 : Les services sont initialisés uniquement à l'utilisation
- Compilation du conteneur optimisée : Plus de dépendances résolues au moment du build
- Cache Twig amélioré : Chargement plus fluide des templates
- Gestion des routes accélérée : Résolution plus rapide
- Support natif de FrankenPHP : Un serveur PHP haute performance basé sur Go et Caddy
# docker-compose.yml avec FrankenPHP
services:
app:
image: dunglas/frankenphp:latest
environment:
FRANKENPHP_CONFIG: "worker ./bin/frankenphp-worker.php"
Définition des fonctions et filtres Twig avec attributs
Plus besoin d'étendre une classe de base pour créer des extensions Twig. Les fonctions et filtres sont désormais définis avec des attributs et chargés en lazy-loading :
<?php
use Twig\Attribute\AsTwigFilter;
use Twig\Attribute\AsTwigFunction;
class AppExtension
{
#[AsTwigFilter('product_number')]
public function formatProductNumber(string $number): string
{
return 'PRD-' . strtoupper($number);
}
#[AsTwigFunction('current_user')]
public function getCurrentUser(): ?User
{
// ...
}
}
Nouvelles contraintes de validation
Symfony 8 enrichit son système de validation avec de nouvelles contraintes intégrées :
- Validation de charsets
- Validation d'adresses MAC
- Validation de numéros de semaine ISO
- Comptage de mots
- Validation de syntaxe YAML
- Validation de slugs
- Validation de syntaxe Twig
- Validation de fichiers vidéo
Ressources et Documentation