Comprendre la stack
Le rôle de chaque outil, à partir du prototype que vous avez sous les yeux.
-
Symfony
Reçoit la requête, lit les filtres, prépare les fixtures et choisit la réponse.
-
Twig
Rend le HTML avec des composants maison : props, blocs et attributs fusionnés.
-
Turbo
Remplace une page ou une région par du HTML serveur, sans API JSON.
-
Stimulus
Ajoute les comportements locaux : focus, chargement, thème, dialogues.
Un exemple : ouvrir une mission
Dans Missions, le lien d’une ligne pointe vers /missions?selected=1. Il vit dans la Frame
mission-results : Turbo n’échange que cette région et met l’URL à jour. Symfony valide les
paramètres et redirige une sélection hors filtre. Stimulus place le focus sur le titre de la fiche. Sans
JavaScript, le même lien charge la page complète.
Symfony : le contrôleur prépare les données
Un contrôleur par rubrique, quelques lignes chacun. Les paramètres d’URL arrivent par
#[MapQueryParameter] (un paramètre non scalaire répond 400), le filtrage vit dans le domaine
(MissionFilter, enum MissionStatus) et les données passent par l’interface
WorkspaceRepository, servie ici par des fixtures en mémoire.
#[Route('', name: 'index', methods: ['GET'])]
public function index(
#[MapQueryParameter(validationFailedStatusCode: 400)] string $q = '',
#[MapQueryParameter(validationFailedStatusCode: 400)] string $status = '',
): Response {
$filter = new MissionFilter($q, MissionStatus::tryFrom($status));
return $this->render('mission/index.html.twig', [
'missions' => $this->workspace->findMissions($filter),
]);
}
Documentation Symfony 7.4 : contrôleurs
Twig et TwigComponent : un design system de composants maison
Toutes les pages héritent de base.html.twig. Les primitives Ui:* et les composants
métier Mission:*, Document:* sont des composants anonymes : des props déclarées par
{% props %}, un bloc
content et fusionnent les attributs avec attributes.defaults() : un
data-action Stimulus ou une classe passés par l’appelant sont conservés. La rosette
Ui:Guilloche est une classe PHP qui calcule ses courbes ; le gabarit les lit via
computed pour ne les calculer qu’une fois par rendu.
<twig:Ui:Button href="{{ path('app_document_index') }}" intent="secondary">
Ouvrir les documents
</twig:Ui:Button>
<twig:Ui:Guilloche :seed="mission.id" :progress="mission.progress" :size="44" />
Documentation Symfony UX TwigComponent
Turbo : navigation, régions et transitions
Turbo Drive charge les pages ; la balise view-transition lui laisse animer le passage d’une page à
l’autre avec l’API View Transitions, le rail restant immobile. Les Frames mission-results et
document-results ne remplacent que la liste et la fiche après un filtre GET.
<meta name="view-transition" content="same-origin">
<turbo-frame id="mission-results" data-turbo-action="advance">
<!-- Liste et fiche rendues par Twig -->
</turbo-frame>
Documentation Turbo : Frames
Stimulus : petites interactions et cycle de vie
filtered_results_controller.js gère chargement, erreur et focus de la région filtrée de Missions et
de Documents, branché dans Twig par stimulus_controller() et stimulus_action(). Le
skeleton n’apparaît qu’après 140 ms de requête, jamais pour une réponse rapide.
theme_controller.js révèle le thème depuis le bouton. Chaque minuteur est nettoyé à la
déconnexion et avant la mise en cache Turbo.
export default class extends Controller {
static targets = ['skeleton', 'content'];
start(event) {
this.skeletonTimer = setTimeout(() => this.showSkeleton(), SKELETON_DELAY);
}
disconnect() { this.stopLoading(); }
}
Documentation Stimulus : cycle de vie
SCSS et Encore : des tokens aux composants
Les styles sont des modules @use rangés par couche : settings (primitives de marque,
rôles sémantiques, points de rupture), base, components (un partiel par
composant Ui), layout, domain, pages et motion. Les
composants ne lisent que des propriétés CSS sémantiques : le thème sombre redéfinit les rôles, pas les
composants. Les listes s’adaptent à leur conteneur avec des container queries.
// settings/_tokens.scss
:root { --action: #{brand.$green}; --copper: #8c4810; }
[data-theme='dark'] { --action: #8fcfa2; }
// components/_status.scss
.ui-status--amber { --_ink: var(--copper); }
Documentation Encore
Symfony 7.4, Twig 3, TwigComponent 3.6, Stimulus 3 et Turbo 8 sont utilisés ici. Ce guide explique cette réalisation.