Une meilleure architecture ADR pour vos contrôleurs Symfony
Publié le 01/11/2024 • Actualisé le 01/11/2024
Cet article montre différentes expériences et essais autour de l'architecture ADR appliquée aux contrôleurs Symfony. C'est parti ! 😎
Prérequis
Je présumerai que vous avez les connaissances élémentaires de Symfony et que vous savez ce qu'est un contrôleur.
Configuration
- PHP 8.3
- Symfony 7.1
Introduction
Dans l'article de blog « Une semaine de Symfony » #923, il y avait un article écrit par Damien Alexandre de JoliCode : « Une bonne convention de nommage pour les routes, les contrôleurs et les templates ? ». Comme j'ai toujours été intéressé par les bonnes pratiques et les conventions, il a retenu mon attention. Je vous laisse lire l'article en entier, mais l'un des principaux sujets abordés est :
Pourrait-on utiliser la FQCN des contrôleurs comme nom de route ? 🤔
But
Cet article présente quelques idées intéressantes, mais essayons tout ceci dans un vrai projet Symfony. Pourquoi ne pas aller plus loin ?
MicroSymfony est un template d'application Symfony que j'ai développé et utilisant déjà le pattern ADR ; c'est donc un bon candidat pour tester tout ceci.
L'architecture ADR
ADR correspond à « Action Domain Responder » ; dans Symfony cela peut être implémenté avec des contrôleurs invocables. Comme expliqué dans la documentation :
«Controllers can also define a single action using the__invoke()
method, which is a common practice when following the ADR pattern (Action-Domain-Responder).»🇫🇷 « Les contrôleurs peuvent aussi définir une action grâce à la méthode
__invoke()
, qui est une pratique commune quand on suit l'architecture ADR. »
Voici le snippet extrait de la documentation :
// src/Controller/Hello.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/hello/{name}', name: 'hello')]
class Hello
{
public function __invoke(string $name = 'World'): Response
{
return new Response(sprintf('Hello %s!', $name));
}
}
Peut-on améliorer ceci ? 🤔
POC : utilisation de la FQCN du contrôleur comme nom de route
Dans la documentation, nous voyons que la route est une simple chaîne. Elle est mise en dur. Y a-t-il une convention pour cette chaîne ? On peut mettre ce que l'on veut. Quand l'application grossit, si on a plusieurs développeurs, chacun peut utiliser une convention différente. Comment éviter ceci ?
Quand on utilise l'architecture ADR, chaque action est encapsulée dans une classe de contrôleur contenant une seule méthode __invoke()
. Cela veut dire que le contrôleur identifie l'action et la FQCN identifie de façon unique chaque contrôleur. Pourquoi donc ne pas utiliser la FQCN self::class
comme nom de route ? C'est le point principal de l'article de JoliCode. Le snippet précédent devient :
// src/Controller/Hello.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/hello/{name}', name: self::class)]
class Hello
{
public function __invoke(string $name = 'World'): Response
{
return new Response(sprintf('Hello %s!', $name));
}
}
Vérifions la route avec la commande debug:router
:
$ bin/console debug:router "App\Controller\Hello"
+--------------+---------------------------------------------------------+
| Property | Value |
+--------------+---------------------------------------------------------+
| Route Name | App\Controller\Hello |
| Path | /hello/{name} |
| Path Regex | {^/hello(?:/(?P<name>[^/]++))?$}sDu |
| Host | ANY |
| Host Regex | |
| Scheme | ANY |
| Method | ANY |
| Requirements | NO CUSTOM |
| Class | Symfony\Component\Routing\Route |
| Defaults | _controller: App\Controller\Hello() |
| | name: World |
| Options | compiler_class: Symfony\Component\Routing\RouteCompiler |
| | utf8: true |
+--------------+---------------------------------------------------------+
Comme prévu, le nom de route associé à l'action devient la FQCN du contrôleur 🎉. On peut ainsi bel et bien utiliser la FQCN comme nom de route. De plus, on évite de mettre des chaînes en dur dans le code et on peut appliquer cette convention à tous les autres contrôleurs.
Veuillez noter que si omet le nom de la route, alors Symfony en génère un comme expliqué dans cet article de blog . Dans ce cas, le nom de la route généré sera app_hello__invoke
. Cette fonctionnalité a été introduite dans Symfony 6.4.
Parfait, cela fonctionne, mais pouvons-nous aller plus loin dans cette approche ? Ne pouvons-nous pas utiliser self::class
à d'autres endroits ?
POC : utilisation de la FQCN du contrôleur comme nom et chemin de template Twig
Quand on crée un contrôleur, on peut étendre le contrôleur abstrait Symfony AbstractController
qui fournit quelques fonctions utiles. Notamment, la fonction render()
pour rendre une réponse depuis un template Twig.
Regardons un exemple dans le projet MicroSymfony : le contrôleur HomeAction
affiche la page d'accueil du projet. On utilise la FQCN du contrôleur pour rendre le template Twig.
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Attribute\Cache;
use Symfony\Component\Routing\Attribute\Route;
/**
* @see StaticActionTest
*/
#[AsController]
#[Cache(maxage: 3600, public: true)]
final class HomeAction extends AbstractController
{
/**
* Simple page with some content.
*/
#[Route(path: '/', name: self::class)]
public function __invoke(): Response
{
$readme = (string) file_get_contents(__DIR__.'/../../README.md');
return $this->render(self::class.'.html.twig', ['readme' => $readme]);
}
}
Cela fonctionne, mais on doit modifier le chemin du template dans le projet ; c'est désormais :
templates
App
Controller
HomeAction.html.twig
Veuillez noter qu'on a un peu de magie 🧙 puisque la FQCN est App\Controller\HomeAction
alors que le chemin généré est finalement App/Controller/HomeAction
. Symfony convertit les anti-slash en slashes, car le chemin passé en paramètre est normalisé.
Peut-on faire mieux ? Si on regarde le code du contrôleur, il y a encore des chaînes en dur. Pouvez-vous les voir ?
POC : utilisation de la FQCN du contrôleur comme chemin de route
Prenons comme exemple un autre contrôleur, celui affichant le fichier composer.json
du projet :
#[Route(path: '/composer', name: self::class)]
public function __invoke(): Response
{
$composer = (string) file_get_contents(__DIR__.'/../../composer.json');
return $this->render(self::class.'.html.twig', ['composer' => $composer]);
}
Dans l'attribut #Route
de la fonction __invoke()
, remplaçons la chaîne /composer
de la valeur de path:
par la FQCN de la classe. Le code devient :
#[Route(path: self::class, name: self::class)]
public function __invoke(): Response
{
$composer = (string) file_get_contents(__DIR__.'/../../composer.json');
return $this->render(self::class.'.html.twig', ['composer' => $composer]);
}
Encore une fois, nous pouvons le faire, l'URL devient :
https://127.0.0.1:8001/App\Controller\ComposerAction
L'URL est certes affreuse 😱, mais cela fonctionne ! Devrions-nous utiliser ceci ? Pas sur un site public où le SEO est important. Mais, pour un site interne ou un outil, pourquoi pas ? Ce qui est commode ici, c'est que l'on sait le code qui est utilisé rien qu'en regardant l'URL.
Testez par vous-même !
Vous pouvez tester tout ceci en moins d'une minute ; lancez (composer et le binaire Symfony sont requis) :
composer create-project strangebuzz/microsymfony && cd microsymfony && make start && open https://127.0.0.1:8000
Vous pouvez créer une PR si vous détectez des erreurs ou si quelque chose peut être amélioré. 🙏
Conclusion
J'utilise cette nouvelle architecture dans le projet MicroSymfony (à part pour le chemin des routes), et j'ai introduit un nouveau helper Twig path(ctrl_fqcn('HomeAction'))
afin de ne pas avoir à saisir la FQCN complète des contrôleurs dans les templates Twig quand on veut générer des URL. (ce qui est assez moche puisque qu'on doit utiliser App\\Controller\\ComposerAction
😱).
Essayons et voyons si cette méthode peut convenir à de plus gros projets. Au moins, nous savons que c'est possible. Qu'en pensez-vous ? 🙂
Et voilà ! J'espère que vous avez aimé. Découvrez d'autres informations en rapport à cet article avec les liens ci-dessous. Comme toujours, retours, likes et retweets sont les bienvenus. (voir la boîte ci-dessous) À bientôt ! COil. 😊
GitHub Lire la doc Lire la doc Plus sur le web
Ils m'ont donné leurs retours et m'ont aidé à corriger des erreurs et typos dans cet article, un grand merci à : BernardNgandu 👍
A vous de jouer !
Ces articles vous ont été utiles ? Vous pouvez m'aider à votre tour de plusieurs manières : (cf le tweet à droite pour me contacter )
- Me remonter des erreurs ou typos.
- Me remonter des choses qui pourraient être améliorées.
- Aimez et retweetez !
- Suivez moi sur Twitter Suivez moi sur Twitter
- Inscrivez-vous au flux RSS.
- Cliquez sur les boutons Plus sur Stackoverflow pour me faire gagner des badges "annonceur" 🏅.
Merci d'avoir tenu jusque ici et à très bientôt sur Strangebuzz ! 😉
[🇫🇷] 3ᵉ article de l'année : « Une meilleure architecture ADR pour vos contrôleurs Symfony » https://t.co/8N4bRXman9 Relectures, retours, likes et retweets sont les bienvenus ! 😉 Objectif annuel : 3 / 6 #symfony #php #adr #rad #MicroSymfony
— COil ✊ (@C0il) November 4, 2024