backend / php-laravel
Blade : templates et composants
Explication
Ce que vous allez apprendre
- Structurer un layout partagé avec
@extends,@sectionet@yield - Comprendre pourquoi
{{ }}échappe automatiquement le HTML, et quand{!! !!}devient dangereux - Créer des composants Blade réutilisables avec des props typées via
@props - Utiliser les variables spéciales
$loopdans une boucle@foreach - Injecter du contenu paramétré dans un composant grâce aux slots
Dans quel contexte ?
Une équipe frontend Laravel doit afficher une grille de produits identique sur plusieurs pages (accueil, résultats de recherche, page catégorie), avec un badge visuel différent selon que le produit est en rupture de stock ou non. Plutôt que de dupliquer le HTML de la carte produit à chaque endroit, un composant Blade réutilisable centralise cette logique d'affichage en un seul fichier.
D'abord, un système d'héritage de layout simple mais puissant
Un layout de base (layouts/app.blade.php) définit la structure commune à toutes les pages, avec des zones ouvertes via @yield('contenu'). Chaque vue enfant hérite de ce layout avec @extends('layouts.app') puis remplit ces zones avec @section('contenu') ... @endsection.
Cette approche évite de dupliquer le <head>, la navigation et le pied de page sur chaque vue de l'application — un seul endroit à modifier si le layout global doit changer.
Prérequis
Cette leçon suppose que les contrôleurs et la validation (leçons 9 et 13) sont acquis : Blade affiche généralement des données déjà préparées par un contrôleur.
Ensuite, une protection de sécurité activée par défaut
{{ $variable }} échappe automatiquement tout caractère HTML spécial avant affichage, ce qui protège contre les attaques XSS (injection de scripts malveillants) si la donnée provient d'un utilisateur. {!! $variable !!}, à l'inverse, affiche le contenu brut sans échappement.
Piège dangereux
Utiliser {!! $commentaire !!} pour afficher un commentaire saisi par un utilisateur ouvre directement la porte à une injection XSS stockée : un attaquant pourrait soumettre <script> dans son commentaire, exécuté ensuite dans le navigateur de chaque visiteur qui consulte la page. Réserve {!! !!} exclusivement à du contenu explicitement nettoyé et de confiance.
Il reste à éviter la duplication de blocs HTML répétitifs
Un simple @include('partials.navigation') réutilise un fragment de vue, mais ne permet pas facilement de recevoir des données paramétrées de façon structurée. Les composants Blade (<x-produit-carte>) vont plus loin : ils déclarent des @props typées, se comportent comme de vrais éléments HTML personnalisés, et acceptent du contenu injecté via des slots.
| Mécanisme | Reçoit des données ? | Recommandé pour |
|---|---|---|
@include | Variables du contexte courant | Fragments simples sans logique |
Composant Blade <x-nom> | Props explicites (@props) | Éléments réutilisables avec logique |
Slot ($slot) | Contenu HTML arbitraire | Contenu variable injecté par l'appelant |
Maintenant, un détail qui simplifie beaucoup l'affichage de listes
Dans une boucle @foreach, la variable spéciale $loop donne accès à $loop->first, $loop->last, $loop->iteration sans avoir à gérer un compteur manuel. Utile pour styliser différemment le premier ou le dernier élément d'une liste, ou afficher une numérotation.
Astuce
$attributes->merge(['class' => 'carte-produit']) dans un composant permet à l'appelant d'ajouter ses propres classes CSS sans écraser celles définies par défaut dans le composant — un pattern à connaître pour des composants vraiment réutilisables.
Maintenant que l'affichage est maîtrisé, il reste une question transversale : comment intercepter une requête avant ou après qu'elle atteigne le contrôleur, par exemple pour vérifier une authentification ? C'est exactement le rôle des middlewares, sujet de la prochaine leçon.
Commandes & code
Blade : templates et composants
{{-- resources/views/layouts/app.blade.php : layout de base --}}
<!DOCTYPE html>
<html lang="fr">
<head>
<title>@yield('titre', 'Boutique')</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<nav>@include('partials.navigation')</nav>
<main>
@yield('contenu')
</main>
@stack('scripts') {{-- permet aux vues filles d'injecter du JS spécifique --}}
</body>
</html>{{-- resources/views/produits/index.blade.php --}}
@extends('layouts.app')
@section('titre', 'Nos produits')
@section('contenu')
<h1>Produits disponibles</h1>
{{-- {{ }} échappe automatiquement le HTML (protection XSS) --}}
<p>{{ $produits->total() }} résultats</p>
@if ($produits->isEmpty())
<p>Aucun produit trouvé.</p>
@else
<ul>
@foreach ($produits as $produit)
<li class="@if($produit->stock === 0) epuise @endif">
{{-- x-composant : composant Blade réutilisable, voir plus bas --}}
<x-produit-carte :produit="$produit" />
</li>
@endforeach
</ul>
{{-- pagination automatique avec les liens Bootstrap/Tailwind --}}
{{ $produits->links() }}
@endif
@auth
<p>Connecté en tant que {{ auth()->user()->nom }}</p>
@else
<a href="{{ route('login') }}">Se connecter</a>
@endauth
@endsection
@push('scripts')
<script src="{{ asset('js/produits.js') }}"></script>
@endpush{{-- resources/views/components/produit-carte.blade.php --}}
{{-- Composant Blade avec props typées via la classe correspondante --}}
@props(['produit', 'compact' => false])
<div {{ $attributes->merge(['class' => 'carte-produit']) }}>
<h3>{{ $produit->nom_affiche }}</h3>
<p>{{ number_format($produit->prix, 2) }} €</p>
@unless($compact)
<p>{{ Str::limit($produit->description, 100) }}</p>
@endunless
{{ $slot }} {{-- contenu injecté entre les balises ouvrante/fermante du composant --}}
</div>{{-- Utilisation du composant avec un slot --}}
<x-produit-carte :produit="$produit" :compact="true">
<button wire:click="ajouterAuPanier({{ $produit->id }})">Ajouter au panier</button>
</x-produit-carte>
{{-- Boucle avec variables spéciales $loop --}}
@foreach ($categories as $categorie)
<div class="{{ $loop->first ? 'premiere' : '' }} {{ $loop->last ? 'derniere' : '' }}">
{{ $loop->iteration }}/{{ $loop->count }} — {{ $categorie->nom }}
</div>
@endforeachRésumé
{{ }}échappe le HTML par défaut (protection XSS) ;{!! !!}ne le fait pas (à éviter sauf contenu de confiance).@extends/@section/@yieldstructurent l'héritage de layout.- Les composants Blade (
<x-nom-composant>) avec@propsremplacent avantageusement les@includeparamétrés. $loop->first,$loop->last,$loop->iterationsimplifient les logiques de boucle dans les vues.
Exercices pratiques
Mission : colmater une faille XSS dans les commentaires produits
Objectif : Corriger une vue Blade qui expose une faille XSS et remplacer un fragment duplique par un composant reutilisable.
Contexte
La page produit affiche les commentaires des clients avec {!! $commentaire->texte !!}, decision prise a l'origine pour permettre aux clients de mettre du texte en gras avec des balises HTML simples. Un client a recemment soumis un commentaire contenant une balise <script> qui s'execute desormais dans le navigateur de chaque visiteur de la page produit. Par ailleurs, la carte produit (nom, prix, description tronquee) est copiee-collee dans trois vues differentes (accueil, recherche, categorie), avec de petites divergences qui commencent a apparaitre entre les trois copies.