12. Conclusion
Ce document a présenté la construction d'une application web MVC avec le framework [Symfony] : une application dont les pages HTML sont fabriquées par le serveur, avec des vues [Twig], sans framework [JavaScript] côté navigateur. Il a suivi pas à pas la progression du cours [Laravel], lui-même issu des cours [Flask], [Spring MVC], [ASP.NET Core MVC] et [NestJS] : des actions et du routage vers les vues, les formulaires, l'internationalisation, la sécurité et l'accès aux données, puis une étude de cas complète.
Les mêmes 28 exemples et la même application ont ainsi été écrits avec six frameworks, en [TypeScript], en [C#], en [Java], en [Python] et, deux fois, en [PHP]. Les notions sont les mêmes ; seuls les noms et les mécanismes changent. Les correspondances entre [Laravel] et [Symfony], les deux grands frameworks du monde [PHP] :
[Laravel] | [Symfony] |
[bootstrap/app.php], fournisseurs de services, conteneur | noyau ([src/Kernel.php]), bundles, conteneur compilé ([config/services.yaml], [#[Autowire]]) |
[composer.json], dossier [vendor], [php artisan] | [composer.json], dossier [vendor], [php bin/console] |
fichier [.env], dossier [config] (fichiers [PHP]), fonctions [env] et [config] | fichier [.env], dossier [config/packages] (fichiers YAML), [%env(...)%], paramètres du conteneur |
[routes/web.php], routes nommées, groupes de routes | attributs [#[Route]] des contrôleurs, sur l'action et sur la classe |
[view], [redirect], [route] | [render], [redirectToRoute], [generateUrl] ([AbstractController]) ; [path] dans les vues |
objet [Request], contraintes ([whereNumber]), liaison de modèle | objet [Request], [requirements] ([{id<\d+>}]), résolveurs d'arguments, [#[MapQueryString]], [#[MapRequestPayload]] |
[Validator], requêtes de formulaire ([FormRequest]), règles, [$errors], [old()] | composant Validator (contraintes en attributs), composant Form (types, [handleRequest], [isValid]) |
vues [Blade], [{{ }}], [@foreach], [@if] | vues [Twig], [{{ }}], [{% for %}], [{% if %}] |
[@extends] et sections, [@include], composants ([<x-...>]) | [{% extends %}] et blocs, [{% include %}], macros |
compositeur de vue ([View::composer]) | variables globales de [Twig], extensions [Twig] (fonctions, filtres) |
données flash de la session ([session()->flash], [with]) | messages flash ([addFlash], [app.flashes]), session en fichiers |
middlewares (globaux, de groupe, de route), méthode [terminate] | écouteurs des événements du noyau ([kernel.request], [kernel.controller], [kernel.response], [kernel.terminate]...) |
[withExceptions], [render], [abort] | écouteurs de [kernel.exception], [HttpException], [createNotFoundException] |
gardes ([Auth::viaRequest]), middleware [auth], [Gate], façade [Hash] | pare-feu, authentificateurs, [access_control], [#[IsGranted]], voteurs, hachage ([security.yaml]) |
middleware [PreventRequestForgery], directive [@csrf] (réponse 419) | jeton du composant Form, [csrf_token()] et [isCsrfTokenValid] (réponse 403, ou 419 dans notre application) |
[RateLimiter] (façade) | composant [RateLimiter] (limiteurs déclarés dans [framework.yaml]) |
dossier [lang], fonction [__()], [App::setLocale] | dossier [translations] (YAML, ICU), filtre [trans], [TranslatorInterface], langue de la requête |
[Carbon] ([isoFormat]), [Number] | extension [intl] : [IntlDateFormatter], [NumberFormatter] |
cache ([Cache], [Cache::lock]) | composant Cache (PSR-6 et contrats), composant Lock |
[Eloquent] : modèles Active Record, relations ([belongsTo], [with]) | [Doctrine] : entités Data Mapper, gestionnaire d'entités, DQL et QueryBuilder, relations ([ManyToOne], [OneToOne]) |
[DB::transaction(...)] | [wrapInTransaction(...)], [flush] |
[WHERE version = ?] et [version + 1], écrits dans la couche DAO | [#[ORM\Version]], géré par [Doctrine] ([OptimisticLockException]) |
[php artisan serve] | [php -S localhost:8000 -t public], ou [symfony server:start] |
Par rapport à [Laravel], on retiendra surtout :
- [Symfony] est un ensemble de composants indépendants, assemblés par des bundles. Chaque composant se comprend seul, et [Laravel] en utilise plusieurs ([HttpFoundation], [HttpKernel], [Routing], [Console]...) : le lecteur du cours précédent retrouve ici les mêmes objets [Request] et [Response]. Une application [Symfony] n'installe que les composants dont elle a besoin : les exemples ont commencé avec le seul [framework-bundle], et ont ajouté un à un [Twig], la validation, les formulaires, la sécurité, [Doctrine] ;
- tout passe par le conteneur de services et l'injection de dépendances : pas de façades ni de fonctions globales, mais des services reçus par le constructeur, déclarés d'après leur type (autowiring). Les dépendances d'une classe sont toujours visibles, dans son constructeur. Le conteneur est compilé en code [PHP], et mis en cache : ce qui compense le coût de la reconstruction de l'application à chaque requête (exemple 01) ;
- le traitement d'une requête est une suite d'événements du noyau, auxquels réagissent des écouteurs : c'est là que se placent la langue, la sécurité, les contrôles de l'application (le laissez-passer) et les pages d'erreur. Le mécanisme est plus explicite que les middlewares de [Laravel] : chaque écouteur choisit son événement et sa priorité, et la commande [debug:event-dispatcher] montre l'ordre de tous les écouteurs ;
- la configuration est explicite, dans des fichiers YAML (ou [PHP]) qui décrivent chaque bundle : le pare-feu, les règles d'accès, les limiteurs, la session, [Doctrine]. Les commandes [debug:router], [debug:container], [debug:config] affichent ce que [Symfony] en a compris ;
- les attributs [PHP] décrivent le code : les routes ([#[Route]]), les contraintes ([#[Assert\NotBlank]]), les entités ([#[ORM\Column]]), les écouteurs ([#[AsEventListener]]), les autorisations ([#[IsGranted]]), l'injection ([#[Autowire]]). Nous en avons même écrit ([#[Chronometre]], [#[PasseInscriptionExige]]) ;
- les formulaires sont des objets, décrits par des classes de type : champs, contraintes, conversion des valeurs, réaffichage de la saisie, messages d'erreur, jeton anti-CSRF. C'est plus de code que les requêtes de formulaire de [Laravel] pour un petit formulaire, et beaucoup moins pour un grand : un thème ([Bootstrap] 5) dessine tous les champs de la même façon ;
- la sécurité est un composant à part entière : un pare-feu, des authentificateurs qui établissent l'utilisateur, des règles d'accès, une hiérarchie des rôles, des voteurs. Nous l'avons plié à notre besoin (un jeton JWT dans un cookie) par un authentificateur de quelques dizaines de lignes, sans rien perdre du reste : [access_control], [#[IsGranted]], [is_granted] dans les vues ;
- [Doctrine] suit le modèle Data Mapper : les entités sont des objets [PHP] ordinaires, qui ignorent la base ; c'est le gestionnaire d'entités qui les lit, suit leurs modifications, et les écrit toutes ensemble, dans une transaction, au moment du [flush]. Plus abstrait que l'Active Record d'[Eloquent], il sépare nettement les objets de leur enregistrement, et prend en charge des mécanismes comme le verrou optimiste. Le journal des ordres SQL permet, comme avec [Eloquent], de vérifier ce qui est envoyé à la base ;
- les vues [Twig] ont leur propre langage, plus limité que le [PHP] des vues [Blade] : une vue ne peut qu'afficher les données qu'on lui donne. C'est une contrainte, et une garantie : la logique reste dans les classes. Elles sont compilées en [PHP], mises en cache, et échappent automatiquement ce qu'elles affichent.
L'étude de cas montre qu'une application HTML classique, sans [JavaScript] indispensable, reste une solution simple et robuste : chaque page est une URL, le bouton « précédent » du navigateur fonctionne, et le serveur contrôle tout. Le peu de [JavaScript] utilisé (les fenêtres de confirmation) améliore le confort sans être nécessaire au fonctionnement. [Symfony] sait aussi servir des applications plus interactives, sans quitter [Twig] ([Symfony UX] : [Turbo], [Stimulus], les Live Components) ou avec un framework [JavaScript] côté navigateur, auquel il fournit une API JSON ([API Platform]) : ce sera l'objet d'autres documents.
Pour aller plus loin, on pourra consulter la documentation de [Symfony] (symfony.com/doc), en particulier les pages consacrées au conteneur de services, aux événements du noyau, aux formulaires, à la sécurité et à [Doctrine], celle de [Twig] (twig.symfony.com), celle de [Doctrine] (doctrine-project.org), celle de [PHP] (php.net/manual/fr), et comparer cette application à ses sœurs [Laravel], [Flask], [Spring MVC], [ASP.NET Core MVC] et [NestJS].