Laravel 12 : Construire une Application Commerciale de A à Z
Dernière mise à jour : Novembre 2025
Parcours d'apprentissage
Partie 1 : Démarrage & Fondamentaux
Partie 2 : Modèles, BDD & Eloquent
Partie 3 : Blade, Tailwind & Vite
Partie 4 : Authentification & Sécurité
Partie 5 : Internationalisation (i18n)
Partie 6 : Modules Commerciaux
Partie 7 : Services : Mailing & Queues
Partie 8 : Import/Export & PDF
Partie 9 : Administration & Paramétrage
Partie 10 : API & Intégrations
Partie 11 : Tests, Déploiement & Optimisation
Partie 12 : Bonus : Aller plus loin
Partie 1 : Démarrage & Fondamentaux
Chapitre 1 : Introduction aux Frameworks & Écosystème Laravel
Avant de plonger dans la première ligne de code Laravel, il est essentiel de comprendre pourquoi cet outil existe et en quoi il va radicalement changer votre façon de développer des applications PHP. Ce chapitre pose le contexte : nous définirons ce qu'est un framework, explorerons les alternatives et justifierons pourquoi Laravel est un choix de premier ordre pour les projets modernes.
1.1. Qu'est-ce qu'un Framework PHP ?
Analogie : Construire une application web sans framework, c'est comme construire une maison en fabriquant vous-même chaque brique, chaque tuile et chaque vis. C'est possible, mais c'est lent, répétitif et sujet aux erreurs. Un framework vous fournit un **plan d'architecte (une structure)** et une **caisse à outils de composants préfabriqués et testés (des bibliothèques)**.
Concrètement, un framework PHP vous évite de "réinventer la roue" en fournissant des solutions standardisées pour des problèmes récurrents :
- Le **routage** : Associer une URL (ex: `/contact`) à un fichier PHP spécifique.
- L'**accès à la base de données** : Fournir une couche d'abstraction (ORM) pour ne plus écrire de SQL à la main.
- La **sécurité** : Protéger contre les failles communes (XSS, CSRF, injections SQL).
- La **gestion des templates** : Séparer la logique PHP du code HTML.
- La **gestion des sessions** et de l'**authentification**.
Le problème que cela résout : le chaos du PHP "vanilla"
// Un exemple de code "vanilla" pour une simple page produit
// 1. Connexion manuelle à la BDD (vulnérable si mal gérée)
$pdo = new PDO('mysql:host=localhost;dbname=test', $_ENV['DB_USER'], $_ENV['DB_PASS']);
// 2. Routage basique et non sécurisé
$productId = $_GET['id'] ?? 0;
// 3. Requête SQL manuelle (risque d'injection SQL)
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$productId]);
$product = $stmt->fetch();
// 4. Logique et affichage mélangés
if ($product) {
echo "<h1>" . htmlspecialchars($product['name']) . "</h1>";
} else {
http_response_code(404);
echo "<h1>Produit non trouvé</h1>";
}
?>
Ce code est difficile à maintenir, à sécuriser et à faire évoluer. Un framework impose une structure propre qui résout ces problèmes dès le départ.
1.2. Le Paysage des Frameworks PHP
Le monde PHP est riche et plusieurs frameworks matures coexistent. Les deux acteurs majeurs sur le marché sont :
- Symfony : Très robuste, modulaire et flexible. Il est souvent vu comme un ensemble de "briques logicielles" (composants) que l'on peut utiliser ensemble ou séparément. Sa courbe d'apprentissage est parfois jugée plus raide. De nombreux grands projets (Drupal, PrestaShop) sont basés sur ses composants.
- Laravel : Il est réputé pour son élégance, sa simplicité et sa productivité. Sa philosophie est de fournir une solution "tout-en-un" très cohérente, qui rend le développement rapide et agréable. Il possède l'un des écosystèmes les plus riches (outils officiels pour le frontend, le déploiement, etc.).
D'autres frameworks comme Laminas (anciennement Zend Framework) ou CakePHP existent mais ont une part de marché plus faible aujourd'hui.
1.3. Pourquoi Choisir Laravel ? Avantages et Inconvénients
Le choix d'un framework est souvent une question de philosophie et de besoins. Voici ce qui fait la force de Laravel, ainsi que quelques points de vigilance.
✅ Avantages
- Productivité & Élégance : Sa syntaxe est expressive et concise. Des outils comme l'ORM Eloquent ou le moteur de template Blade permettent de faire beaucoup avec peu de code.
- Écosystème "tout-en-un" : Laravel propose des outils officiels parfaitement intégrés pour l'authentification (Breeze), les API (Sanctum), le temps réel (Reverb), le déploiement (Forge), et bien plus.
- Communauté immense : Une documentation exemplaire, de très nombreux tutoriels, des packages pour tous les besoins et une aide facile à trouver.
- Artisan CLI : Sa ligne de commande est l'une des plus puissantes et des plus utiles pour générer du code et gérer l'application.
- Performances modernes : Avec les versions récentes de PHP, Laravel est devenu très performant et convient à la grande majorité des applications web.
❌ Inconvénients
- La "Magie" : Laravel utilise beaucoup de "Facades" et de mécanismes en arrière-plan qui peuvent rendre la compréhension de son fonctionnement interne difficile pour un débutant. (Nous démystifierons cela !)
- Fortement "opinionné" : Laravel vous guide vers "sa" façon de faire les choses. C'est un avantage pour la productivité, mais cela peut être moins flexible que Symfony si vous avez besoin de sortir des sentiers battus.
- Mises à jour fréquentes : Le cycle de sortie rapide peut être un défi pour la maintenance de très gros projets sur le long terme, bien que des versions LTS (Long Term Support) existent.
Pour notre projet de gestion commerciale et pour la majorité des applications d'entreprise, les avantages de Laravel en termes de rapidité de développement et de clarté du code l'emportent largement. C'est un excellent choix pour apprendre et pour produire.
Chapitre 2 : Installation & Configuration de l'Environnement
Maintenant que nous avons posé les bases théoriques, nous allons passer à la pratique. Ce chapitre est un guide pour installer votre premier projet Laravel 12, configurer votre environnement de développement local et vous familiariser avec la structure des fichiers. L'objectif est simple : voir la page d'accueil de Laravel s'afficher dans votre navigateur.
2.1. Prérequis Techniques
Avant de commencer, assurez-vous que les outils suivants sont installés et accessibles depuis votre terminal. Laravel 12 a des exigences spécifiques :
- PHP >= 8.2 : Laravel 12 requiert une version récente de PHP. Vérifiez votre version avec la commande `php -v`.
- Composer : Le gestionnaire de dépendances pour PHP. Si vous ne l'avez pas, suivez les instructions sur getcomposer.org. Vérifiez votre version avec `composer -V`.
- Node.js >= 18.0 & npm : Nécessaires pour l'écosystème frontend (Vite, Tailwind CSS). Téléchargez-les sur nodejs.org. Vérifiez vos versions avec `node -v` et `npm -v`.
- Un serveur de base de données : MySQL, MariaDB ou PostgreSQL. Assurez-vous d'avoir un outil pour gérer vos bases de données (phpMyAdmin, TablePlus, DBeaver, etc.).
2.2. Création du Projet Laravel
Il existe deux manières principales pour installer un nouveau projet Laravel.
Méthode 1 : Via le Laravel Installer (Recommandée)
C'est la méthode la plus rapide. Elle nécessite d'installer globalement l'outil d'installation de Laravel une seule fois.
# Exécutez cette commande une seule fois sur votre machine pour installer le programme.
composer global require laravel/installer
# Ensuite, pour chaque nouveau projet, utilisez la commande `laravel new`. C'est très rapide.
laravel new mon-projet-commercial
Méthode 2 : Via Composer Create-Project
Cette méthode ne nécessite aucune installation globale. Composer télécharge le squelette de l'application et installe les dépendances dans la foulée.
# Cette commande crée directement le projet.
composer create-project laravel/laravel mon-projet-commercial
Quelle que soit la méthode, naviguez ensuite dans le dossier du projet : `cd mon-projet-commercial`.
2.3. Les Fichiers de Dépendances : `composer.json` et `package.json`
Ces deux fichiers à la racine de votre projet sont la "liste des ingrédients" de votre application. Il est crucial de comprendre leur rôle.
-
`composer.json` : Gère les dépendances **côté serveur (PHP)**.
C'est ici que sont listés le framework Laravel lui-même (`laravel/framework`), les packages pour gérer les bases de données, les API, etc. La section `require-dev` contient les outils utilisés uniquement en développement, comme les bibliothèques de test (PHPUnit).
-
`package.json` : Gère les dépendances **côté client (JavaScript/CSS)**.
Ce fichier liste les outils de build comme Vite, et les bibliothèques frontend comme Tailwind CSS ou Alpine.js. La section `devDependencies` contient tout ce qui est nécessaire pour *construire* vos assets, mais qui ne se retrouvera pas dans le code final envoyé au navigateur.
2.4. Exploration de la Structure des Dossiers
Une fois le projet créé, ouvrez-le dans votre éditeur de code. Voici les dossiers les plus importants à connaître :
- `app/` : Le cœur de votre application (Modèles, Contrôleurs, Services).
- `routes/` : Les fichiers de routes (`web.php`, `api.php`).
- `resources/` : Les Vues (`views/`), les fichiers de langue (`lang/`) et les assets bruts.
- `public/` : Le seul dossier accessible depuis le web, point d'entrée de l'application.
- `database/` : Les migrations, seeders et factories.
- `.env` : Le fichier de configuration de votre environnement local.
2.5. Configuration de l'Environnement (.env)
Étape 1 : Créer la base de données
Avec votre outil de gestion de BDD, créez une nouvelle base de données vide. Nommons-la `mon_projet_commercial` (en utilisant l'encodage `utf8mb4_unicode_ci`).
Étape 2 : Configurer le fichier `.env`
Ouvrez le fichier `.env` et modifiez les variables de la base de données (`DB_...`) pour qu'elles correspondent à votre configuration locale.
# ...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mon_projet_commercial # <-- Le nom que vous venez de créer
DB_USERNAME=root # <-- Votre utilisateur BDD
DB_PASSWORD= # <-- Votre mot de passe BDD
2.6. Compilation des Assets : `dev` vs `build`
Dans le fichier `package.json`, vous verrez des "scripts" comme `"dev"` et `"build"`. Ils servent à compiler vos fichiers CSS et JS, mais dans des contextes différents.
-
`npm run dev` : Pour le développement
Cette commande lance un serveur de développement (Vite). Il surveille vos fichiers (`.css`, `.js`, `.blade.php`) et, dès que vous sauvegardez une modification, il recompile instantanément les assets et rafraîchit votre navigateur (Hot Module Replacement). C'est extrêmement rapide et pratique. Les fichiers générés ne sont pas optimisés.
-
`npm run build` : Pour la production
Cette commande fait un travail différent : elle lit tous vos assets, les optimise (minification, suppression du code inutilisé), les combine en de petits fichiers et les place dans le dossier `public/build`. C'est cette version optimisée que vous déploierez sur votre serveur de production.
2.7. Lancement des Serveurs de Développement
Vous aurez besoin de **deux terminaux ouverts** à la racine de votre projet.
Étape 1 : Installer les dépendances frontend
# Cette commande lit package.json et installe Vite, Tailwind, etc. dans le dossier `node_modules`.
npm install
Étape 2 : Lancer les serveurs
# Dans le Terminal 1 : Lancez le serveur Vite pour les assets.
npm run dev
# Dans le Terminal 2 : Lancez le serveur PHP avec Artisan.
php artisan serve
Artisan vous donnera une URL, généralement `http://127.0.0.1:8000`. Ouvrez-la dans votre navigateur. Si tout est correct, vous devriez voir la page d'accueil de Laravel 12.
Atelier Pratique : Validation de l'installation
Cet exercice est une checklist pour s'assurer que votre environnement est parfaitement opérationnel pour la suite du cours.
Checklist de démarrage
- Valider les prérequis : Confirmez que les commandes `php -v`, `composer -V`, `node -v` et `npm -v` retournent des versions compatibles.
- Installer le projet : Utilisez `laravel new mon-projet-commercial` ou `composer create-project...`.
- Préparer la base de données : Créez une base de données nommée `mon_projet_commercial`.
- Configurer l'environnement : Remplissez les informations `DB_*` dans votre fichier `.env`.
- Générer la clé d'application : Exécutez `php artisan key:generate` pour vous assurer que la variable `APP_KEY` dans le fichier `.env` est bien définie.
- Installer les dépendances Node : Exécutez `npm install`.
- Lancer les serveurs : Lancez `npm run dev` dans un terminal et `php artisan serve` dans un second.
- Vérifier le résultat : Rendez-vous à l'adresse `http://127.0.0.1:8000`. Vous devez voir la page d'accueil de Laravel.
🎉 Bravo ! Si vous voyez la page d'accueil, votre environnement de développement est prêt. Vous êtes paré pour commencer à construire notre application.
Chapitre 3 : Architecture & Cycle de Vie d'une Requête
Votre projet Laravel est installé et fonctionnel. Mais que se passe-t-il exactement lorsque vous visitez `http://127.0.0.1:8000` ? Comment Laravel sait-il quelle page afficher ? Ce chapitre lève le voile sur la "magie" en décortiquant le cheminement d'une requête HTTP, du navigateur de l'utilisateur jusqu'à l'affichage de la page. Nous mettrons ensuite cette connaissance en pratique en créant nos premières pages personnalisées.
3.1. Le Cycle de Vie d'une Requête : Le Grand Voyage
Analogie : Imaginez que vous commandez un plat dans un restaurant. La requête HTTP est votre commande. Laravel est toute la cuisine organisée qui la traite.
- Le Client (Vous) passe une Commande : Vous tapez `http://mon-site.com/produits` dans votre navigateur et appuyez sur Entrée.
- Le Point d'Entrée (`public/index.php`) : Votre commande arrive au seul endroit public du restaurant : le comptoir. Toutes les requêtes, sans exception, passent par ce fichier.
- Le "Noyau" (Kernel) prépare la Cuisine : Le fichier `index.php` charge le "Noyau" (Kernel) de Laravel. Ce dernier démarre tous les services essentiels de l'application (le Service Container, la gestion des erreurs, les logs...).
- Le Maître d'Hôtel (Routeur) lit la Commande : Le Noyau passe la requête au Routeur. Le Routeur regarde votre commande (`/produits`) et consulte son carnet (`routes/web.php`) pour savoir quel cuisinier doit la préparer.
- Le Cuisinier (Contrôleur) prépare le Plat : Le Routeur a trouvé une correspondance ! Il appelle la méthode `index()` du `ProductController`. C'est le Contrôleur qui orchestre la préparation.
- Le Contrôleur utilise les Ingrédients (Modèles) : Le `ProductController` demande au `Product` (Modèle) de lui donner la liste de tous les produits de la base de données.
- Le Plat est mis en forme (Vue) : Le Contrôleur, avec la liste des produits, la transmet à un "présentateur" (`produits.blade.php`). Cette Vue met en forme les données dans un plat final (le code HTML).
- Le Serveur vous apporte le Plat (Réponse) : La Vue génère une réponse HTML complète, qui est renvoyée à votre navigateur pour être affichée.
3.2. Le Routage : Le Gardien de votre Application (`routes/web.php`)
Le fichier `routes/web.php` est le carnet de routes de votre application web. Il contient une liste d'URLs et dit à Laravel quoi faire pour chacune d'elles. C'est ici que nous allons définir la structure de notre site.
Commençons par modifier la route existante pour la faire pointer vers un contrôleur que nous allons créer.
// Fichier : routes/web.php
use Illuminate\Support\Facades\Route;
// On importe le contrôleur que nous allons bientôt créer.
use App\Http\Controllers\HomeController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
*/
// Au lieu de retourner une vue directement, on dit à Laravel :
// "Quand un utilisateur demande la racine '/', exécute la méthode 'index' de HomeController."
Route::get('/', [HomeController::class, 'index']);
3.3. Les Contrôleurs : Les Chefs d'Orchestre (`app/Http/Controllers`)
Le contrôleur ne contient pas de HTML ou de SQL. Son seul rôle est de recevoir une requête, de coordonner les actions nécessaires (parler aux modèles, etc.) et de renvoyer une réponse. Utilisons Artisan pour créer notre `HomeController`.
# Cette commande crée un nouveau fichier : app/Http/Controllers/HomeController.php
php artisan make:controller HomeController
Modifions maintenant ce fichier pour y ajouter notre méthode `index`.
// Fichier : app/Http/Controllers/HomeController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
// On importe la classe View pour être explicite.
use Illuminate\View\View;
class HomeController extends Controller
{
/**
* Affiche la page d'accueil.
*/
public function index()
{
// Le contrôleur demande à Laravel de retourner la vue qui se trouve dans
// resources/views/welcome.blade.php
return view('welcome');
}
}
Si vous actualisez votre navigateur, la page d'accueil devrait toujours s'afficher. Rien n'a changé visuellement, mais nous avons mis en place une architecture MVC propre !
3.4. Les Vues & Blade : L'Interface Utilisateur (`resources/views`)
Les vues sont de simples fichiers HTML avec des super-pouvoirs. Le moteur de template de Laravel, **Blade**, nous permet d'y injecter de la logique PHP (variables, conditions, boucles) de manière simple et lisible.
Modifions notre `HomeController` pour passer des données à la vue.
// Fichier : app/Http/Controllers/HomeController.php
// ...
public function index()
{
$nom = 'Oussama';
$titre = 'Bienvenue sur notre application de gestion';
// Le 2ème argument de la fonction view() est un tableau de données.
// La clé ('pageTitle') sera le nom de la variable dans la vue.
return view('welcome', [
'pageTitle' => $titre,
'userName' => $nom
]);
}
// ...
Maintenant, affichons ces données dans notre vue `welcome.blade.php`.
<!-- Fichier : resources/views/welcome.blade.php -->
<!DOCTYPE html>
<html lang="fr">
<head>
<title>{{ $pageTitle }}</title>
</head>
<body>
<h1>Bonjour, {{ $userName }} !</h1>
<p>{{ $pageTitle }}</p>
</body>
</html>
Atelier Pratique : Créer vos Premières Pages
Nous allons maintenant appliquer ce que nous venons d'apprendre pour créer deux pages statiques pour notre application : une page "Contact" et une page "À Propos". Je vais vous fournir le code complet pour la page "Contact", et votre exercice sera de créer la page "À Propos" en suivant exactement le même modèle.
Mise en place de la page "Contact"
1. Créer le Contrôleur
php artisan make:controller ContactController
2. Remplir le Contrôleur (`app/Http/Controllers/ContactController.php`)
namespace App\Http\Controllers;
use Illuminate\View\View;
class ContactController extends Controller
{
public function index(): View
{
return view('contact');
}
}
3. Créer la Vue (`resources/views/contact.blade.php`)
<!DOCTYPE html>
<html lang="fr">
<head>
<title>Contactez-nous</title>
</head>
<body>
<h1>Page de Contact</h1>
<p>Vous pouvez nous contacter à l'adresse contact@gestcom.ma</p>
</body>
</html>
4. Ajouter la Route (`routes/web.php`)
// ... au début du fichier, n'oubliez pas d'importer le contrôleur
use App\Http\Controllers\ContactController;
// ... ajoutez cette ligne à la fin du fichier
Route::get('/contact', [ContactController::class, 'index']);
Maintenant, visitez `http://127.0.0.1:8000/contact`. Votre page doit s'afficher !
Exercice : Créer la page "À Propos"
Votre mission est de créer une nouvelle page accessible à l'URL `/a-propos`. Suivez les 4 mêmes étapes que pour la page "Contact" :
- Créez un `AProposController` avec la commande `artisan`.
- Ajoutez une méthode `index()` dans ce contrôleur qui retourne une vue nommée `a-propos`.
- Créez le fichier de vue `resources/views/a-propos.blade.php` avec un contenu simple.
- Ajoutez la route `Route::get('/a-propos', ...)` dans votre fichier `routes/web.php`.
1. Créer le Contrôleur avec Artisan
php artisan make:controller AProposController
2. Remplir le Contrôleur (`app/Http/Controllers/AProposController.php`)
namespace App\Http\Controllers;
use Illuminate\View\View;
class AProposController extends Controller
{
public function index(): View
{
return view('a-propos');
}
}
3. Créer la Vue (`resources/views/a-propos.blade.php`)
<!DOCTYPE html>
<html lang="fr">
<head>
<title>À Propos de Nous</title>
</head>
<body>
<h1>À Propos de notre Société</h1>
<p>Nous sommes une entreprise spécialisée dans la gestion commerciale.</p>
</body>
</html>
4. Ajouter la Route (`routes/web.php`)
// ... au début du fichier, importez le nouveau contrôleur
use App\Http\Controllers\AProposController;
// ... ajoutez cette ligne à la suite des autres routes
Route::get('/a-propos', [AProposController::class, 'index']);
Une fois ces 4 étapes réalisées, visitez `http://127.0.0.1:8000/a-propos` pour vérifier le résultat.
Partie 2 : Modèles, Base de Données & Eloquent
Chapitre 4 : Migrations, Factories & Seeders
Nous allons construire les fondations de notre application : sa base de données. Au lieu de créer des tables manuellement, nous allons découvrir la méthode professionnelle utilisée par les équipes de développement : les migrations pour structurer la base de données, et les factories/seeders pour la peupler avec des données de test de haute qualité.
4.1. Migrations : Le Versionnement de votre Base de Données
Analogie : Pensez aux migrations comme à un "Git" pour votre base de données. Chaque migration est un fichier PHP qui décrit une modification du schéma. Cela permet de garder un historique des changements et de reconstruire la base de données de n'importe qui en une seule commande.
Création d'une table
php artisan make:migration create_products_table
Remplissons la méthode `up()` du fichier de migration généré dans `database/migrations/`.
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 8, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});
}
4.2. Exécution et Annulation des Migrations
# Exécute les nouvelles migrations
php artisan migrate
# Annule la dernière migration
php artisan migrate:rollback
# Annule les 3 dernières migrations
php artisan migrate:rollback --step=3
# Supprime toutes les tables et relance toutes les migrations
php artisan migrate:fresh
4.3. Modification de Tables Existantes
Pour modifier une table, on crée une **nouvelle** migration. Installez d'abord le package `doctrine/dbal` avec `composer require doctrine/dbal` pour permettre la modification de colonnes.
php artisan make:migration modify_products_table
Modifions le nouveau fichier de migration :
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->boolean('is_active')->default(true)->after('stock'); // Ajouter une colonne
$table->decimal('price', 10, 2)->change(); // Modifier une colonne
});
}
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->decimal('price', 8, 2)->change();
$table->dropColumn('is_active'); // Supprimer une colonne
});
}
4.4. Seeders : La Méthode Manuelle
Les Seeders sont des classes dont le seul but est de peupler la base de données. Commençons par la méthode la plus simple : l'insertion manuelle.
1. Créer le Seeder
php artisan make:seeder ProductSeeder
2. Remplir le Seeder avec des données
// Fichier : database/seeders/ProductSeeder.php
use Illuminate\Support\Facades\DB;
public function run(): void
{
DB::table('products')->insert([
[ 'name' => 'Laptop Pro', 'price' => 1299.99, /* ... */ ],
[ 'name' => 'Souris Ergonomique', 'price' => 79.50, /* ... */ ]
]);
}
Cette méthode fonctionne, mais elle est fastidieuse et les données sont toujours les mêmes.
3. Enregistrer et Exécuter le Seeder
Pour que Laravel exécute ce seeder, il faut l'appeler depuis le fichier principal `DatabaseSeeder.php`.
// Fichier : database/seeders/DatabaseSeeder.php
public function run(): void
{
$this->call([
ProductSeeder::class,
]);
}
Vous pouvez maintenant lancer le "seeding" avec : `php artisan db:seed`.
4.5. Factories : La Méthode Professionnelle
Une approche bien plus puissante consiste à utiliser des **Factories**. Ce sont des "usines à données" qui définissent une "recette" pour créer un modèle avec des données factices mais réalistes (noms, paragraphes, prix aléatoires...) grâce à la bibliothèque Faker.
1. Créer le Modèle et sa Factory
# Crée le modèle `app/Models/Product.php` ET sa factory `database/factories/ProductFactory.php`
php artisan make:model Product -f
2. Configurer la Factory
// Fichier : database/factories/ProductFactory.php
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->words(3, true),
'description' => $this->faker->paragraph(),
'price' => $this->faker->randomFloat(2, 10, 1000),
'stock' => $this->faker->numberBetween(0, 200),
'is_active' => $this->faker->boolean(90)
];
}
}
3. Utiliser la Factory dans le Seeder
Il suffit maintenant de refactoriser notre `ProductSeeder` pour qu'il utilise cette factory. C'est beaucoup plus simple !
// Fichier : database/seeders/ProductSeeder.php
use App\Models\Product; // N'oubliez pas d'importer le modèle
public function run(): void
{
// Crée 50 produits avec des données réalistes et aléatoires.
Product::factory()->count(50)->create();
}
La commande `php artisan migrate:fresh --seed` devient maintenant votre meilleure alliée pour réinitialiser et repeupler toute la base de données en un instant.
Atelier Pratique : Lier les Produits et les Catégories
Votre mission est de finaliser la structure de base en créant la table `categories` et en liant chaque produit à une catégorie, en utilisant la méthode professionnelle avec les factories.
Exercice : Mettre en place la relation `products` <> `categories`
- Créez un modèle `Category` avec sa migration, sa factory et son seeder en une seule commande.
- Remplissez la migration `create_categories_table` avec une colonne `name`.
- Configurez la `CategoryFactory` pour générer un nom de catégorie (ex: `fake()->unique()->jobTitle()`).
- Configurez le `CategorySeeder` pour créer 10 catégories en utilisant sa factory.
- Créez une **nouvelle migration** pour ajouter une colonne `category_id` à la table `products` et en faire une clé étrangère.
- Mettez à jour la `ProductFactory` pour qu'elle assigne un ID de catégorie aléatoire à chaque produit créé.
- Mettez à jour le `DatabaseSeeder` pour appeler le `CategorySeeder` **avant** le `ProductSeeder`.
- Lancez `php artisan migrate:fresh --seed` et vérifiez le résultat.
1. Créer les fichiers pour `Category`
php artisan make:model Category -mfs2 & 3. Remplir migration et factory de `Category`
Fichier `..._create_categories_table.php` : ajoutez `$table->string('name');`.
Fichier `CategoryFactory.php` : dans `definition()`, retournez `['name' => $this->faker->unique()->jobTitle()]`.
4. Remplir `CategorySeeder.php`
use App\Models\Category;
public function run(): void { Category::factory(10)->create(); }5. Créer la migration pour la clé étrangère
php artisan make:migration add_category_id_to_products_tableContenu de la migration (`up()`):
Schema::table('products', function (Blueprint $table) {
$table->foreignId('category_id')->nullable()->after('id')->constrained()->onDelete('set null');
});
6. Mettre à jour `ProductFactory.php`
use App\Models\Category;
// ... dans la méthode definition()
'category_id' => Category::inRandomOrder()->first()->id,
7. Mettre à jour `DatabaseSeeder.php`
public function run(): void
{
$this->call([
CategorySeeder::class, // Les catégories doivent exister avant les produits !
ProductSeeder::class,
]);
}
8. Lancer la commande finale
php artisan migrate:fresh --seedChapitre 5 : Eloquent – Les Bases (CRUD)
Nous avons une structure de base de données solide grâce aux migrations. Il est maintenant temps d'interagir avec elle. Eloquent est l'ORM (Object-Relational Mapper) de Laravel. C'est un traducteur intelligent qui vous permet de dialoguer avec vos tables SQL en utilisant des objets PHP, sans écrire une seule ligne de SQL (ou presque). Ce chapitre couvre les opérations fondamentales : Créer, Lire, Mettre à jour et Supprimer des données (CRUD).
5.1. Le Modèle Eloquent : Votre Représentant de Table
Le cœur d'Eloquent est le **Modèle**. Chaque modèle que vous créez dans votre dossier `app/Models/` est directement lié à une table dans votre base de données. Laravel utilise des conventions pour simplifier cette liaison :
- Un modèle `Product` correspondra à la table `products`.
- Un modèle `ProductCategory` correspondra à la table `product_categories`.
Nous avons déjà créé nos modèles `Product` et `Category` à l'étape précédente. Un modèle vide est déjà fonctionnel, mais pour des raisons de sécurité, nous devons y ajouter une propriété essentielle.
5.2. Sécurité : La Protection contre l'Assignation de Masse (`Mass Assignment`)
Imaginez que vous permettez à un utilisateur de s'inscrire avec un nom et un email. Si un utilisateur malveillant ajoute `is_admin=1` dans les données envoyées, la méthode `User::create($request->all())` pourrait accidentellement lui donner des droits administrateur. C'est une faille de "Mass Assignment".
Pour éviter cela, Laravel exige que vous déclariez explicitement quels champs de votre table peuvent être remplis via des méthodes comme `create()` ou `update()`. C'est le rôle de la propriété `$fillable`.
// Fichier : app/Models/Product.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'description',
'price',
'stock',
'is_active',
'category_id',
];
}
Désormais, seules les colonnes listées dans `$fillable` pourront être assignées en masse, protégeant ainsi votre application.
5.3. Lire les Données (Read)
C'est l'opération la plus courante. Eloquent la rend incroyablement simple.
use App\Models\Product;
// 1. Récupérer TOUS les produits
$products = Product::all(); // Renvoie une Collection d'objets Product
// 2. Trouver un produit par sa clé primaire (id)
$product = Product::find(5); // Renvoie un seul objet Product, ou null s'il n'est pas trouvé
// 3. Trouver un produit ou échouer (génère une erreur 404 si non trouvé) - MEILLEURE PRATIQUE
$product = Product::findOrFail(5);
// 4. Construire une requête plus complexe
$activeProducts = Product::where('is_active', true)
->orderBy('price', 'desc')
->take(10)
->get(); // ->get() exécute la requête et renvoie une Collection
// 5. Obtenir le premier résultat d'une requête
$firstExpensiveProduct = Product::where('price', '>', 500)->first();
5.4. Créer, Mettre à Jour & Supprimer (C-U-D)
Ces opérations sont tout aussi intuitives.
use App\Models\Product;
// --- CRÉER un enregistrement (Create) ---
// Méthode 1: En utilisant `create` (nécessite la propriété $fillable)
$newProduct = Product::create([
'name' => 'Nouveau Clavier Mécanique',
'price' => 149.99,
'stock' => 150,
'category_id' => 1
]);
// --- METTRE À JOUR un enregistrement (Update) ---
// Méthode 1: Trouver, modifier, puis sauvegarder
$productToUpdate = Product::findOrFail(10);
$productToUpdate->stock = 75;
$productToUpdate->is_active = false;
$productToUpdate->save();
// Méthode 2: Mise à jour en masse (Mass Update)
Product::where('stock', '<', 10)->update(['is_active' => false]);
// --- SUPPRIMER un enregistrement (Delete) ---
// Méthode 1: Trouver puis supprimer
$productToDelete = Product::findOrFail(20);
$productToDelete->delete();
// Méthode 2: Suppression par clé primaire
Product::destroy(21);
// Méthode 3: Suppression en masse
Product::where('is_active', false)->delete();
Atelier Pratique : Afficher la Liste des Produits
Il est temps de connecter toutes les pièces. Nous allons créer notre première page dynamique : la liste des produits, récupérée directement depuis la base de données grâce à Eloquent.
Mise en place de la page de liste des produits
1. Créer le `ProductController`
# L'option --resource génère un contrôleur avec les méthodes standards du CRUD (index, create, store, etc.)
php artisan make:controller ProductController --resource
2. Définir la route dans `routes/web.php`
use App\Http\Controllers\ProductController;
// Cette seule ligne crée toutes les routes nécessaires pour un CRUD complet sur les produits.
Route::resource('products', ProductController::class);
Vous pouvez voir la liste des routes créées avec la commande `php artisan route:list`.
3. Remplir la méthode `index` du `ProductController`
// Fichier: app/Http/Controllers/ProductController.php
use App\Models\Product;
use Illuminate\View\View;
public function index(): View
{
$products = Product::all(); // On récupère tous les produits
return view('products.index', ['products' => $products]);
}
Le nom de la vue `products.index` correspond au fichier `resources/views/products/index.blade.php`.
4. Créer la Vue (`resources/views/products/index.blade.php`)
Créez un dossier `products` dans `resources/views`, puis le fichier `index.blade.php` à l'intérieur.
<!DOCTYPE html>
<html lang="fr">
<head><title>Liste des Produits</title></head>
<body>
<h1>Nos Produits</h1>
<ul>
@foreach ($products as $product)
<li>
{{ $product->name }} - {{ $product->price }} €
</li>
@endforeach
</ul>
</body>
</html>
Maintenant, visitez `http://127.0.0.1:8000/products`. La liste des 50 produits de votre base de données doit s'afficher !
Exercice : Créer la page de détail d'un produit
Votre mission est de rendre la page de détail d'un produit accessible. Par exemple, l'URL `/products/5` devra afficher toutes les informations du produit qui a l'ID 5.
1. Remplir la méthode `show` du `ProductController`
// La route `Route::resource` a déjà défini que cette méthode recevra l'ID du produit.
// Mieux encore, Laravel peut automatiquement trouver le produit pour nous (Route Model Binding).
public function show(Product $product): View
{
// Pas besoin de faire `Product::findOrFail($id)` ! Laravel le fait pour nous.
return view('products.show', ['product' => $product]);
}
2. Créer la Vue (`resources/views/products/show.blade.php`)
<!DOCTYPE html>
<html lang="fr">
<head><title>{{ $product->name }}</title></head>
<body>
<h1>{{ $product->name }}</h1>
<p>Description : {{ $product->description }}</p>
<p>Prix : {{ $product->price }} €</p>
<p>Stock : {{ $product->stock }} unités</p>
<a href="/products">Retour à la liste</a>
</body>
</html>
3. Mettre à jour la liste pour inclure les liens
Modifiez `products/index.blade.php` pour que chaque produit soit un lien vers sa page de détail.
<!-- ... dans la boucle @foreach de index.blade.php -->
<li>
<a href="/products/{{ $product->id }}">
{{ $product->name }}
</a>
- {{ $product->price }} €
</li>
Maintenant, sur la page `/products`, vous pouvez cliquer sur n'importe quel produit pour voir sa page de détail.
Chapitre 6 : Eloquent – Avancé (Relations, Scopes & Accessors)
Maintenant que nous savons manipuler une table, il est temps de gérer ce qui fait la force d'une base de données : les liens entre les tables. Ce chapitre est consacré aux **relations Eloquent**. Nous apprendrons à définir et à utiliser les relations entre nos modèles, à optimiser drastiquement nos requêtes pour éviter des problèmes de performance, et à écrire un code plus propre et réutilisable grâce aux Scopes et aux Accessors.
6.1. Définir les Relations : `belongsTo` et `hasMany`
Nous avons une colonne `category_id` dans notre table `products`. Pour qu'Eloquent comprenne ce lien, nous devons le déclarer dans nos modèles. C'est une relation "Un-à-Plusieurs" (One-to-Many) : une catégorie **a plusieurs** produits, et un produit **appartient à** une seule catégorie.
1. La relation `belongsTo` (appartient à) dans le modèle `Product`
// Fichier: app/Models/Product.php
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Product extends Model
{
// ...
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}
2. La relation `hasMany` (a plusieurs) dans le modèle `Category`
// Fichier: app/Models/Category.php
use Illuminate\Database\Eloquent\Relations\HasMany;
class Category extends Model
{
// ...
protected $fillable = ['name'];
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
}
Maintenant, nous pouvons naviguer dans nos données de manière totalement intuitive :
$product = Product::find(1);
echo $product->category->name; // Accède au nom de la catégorie du produit
$category = Category::find(1);
foreach ($category->products as $product) { echo $product->name; }
6.2. Le Problème N+1 et l'Eager Loading
C'est **le concept le plus important de ce chapitre**. Utiliser les relations de manière naïve peut détruire les performances. Si vous affichez une liste de 50 produits et leur catégorie dans une boucle (`$product->category->name`), Laravel exécutera **51 requêtes SQL** (1 pour les produits + 50 pour chaque catégorie). C'est le problème N+1.
La solution : Eager Loading (Chargement anticipé)
On dit à Eloquent de charger les relations à l'avance avec la méthode `with()`.
// Ce code exécute 2 requêtes au lieu de 51. C'est infiniment plus performant.
$products = Product::with('category')->get();
**Prenez l'habitude d'utiliser `with()` systématiquement lorsque vous accédez à des relations dans une boucle.**
6.3. Query Scopes : Rendre vos Requêtes Réutilisables
Au lieu de réécrire `->where('is_active', true)` partout, vous pouvez créer un raccourci dans votre modèle appelé **Scope**.
// Fichier: app/Models/Product.php
public function scopeActive(Builder $query): void
{
$query->where('is_active', true);
}
Utilisation : `Product::active()->get();`
6.4. Accessors & Mutators : Transformer les Données
Ce sont des "méthodes magiques" pour formater les attributs de vos modèles. Un **Accessor** (`get`) formate une donnée pour l'affichage. Un **Mutator** (`set`) la transforme avant de la sauvegarder.
// Fichier: app/Models/Product.php
protected function name(): Attribute
{
return Attribute::make(
get: fn ($value) => strtoupper($value), // Toujours afficher en majuscules
set: fn ($value) => ucfirst(strtolower($value)), // Toujours sauvegarder avec une capitale
);
}
6.5. Relation Un-à-Un (`hasOne`)
Analogie : Un produit a une seule fiche de détails techniques. Nous allons créer une table `product_details` pour cela.
Atelier : Créer la table des détails de produits
1. Créez le modèle `ProductDetail` avec sa migration : `php artisan make:model ProductDetail -m`
2. Modifiez la migration pour y inclure une clé étrangère **unique**.
Schema::create('product_details', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->unique()->constrained()->onDelete('cascade');
$table->string('part_number'); $table->float('weight_kg');
$table->timestamps();
});
3. Définissez les relations dans les modèles.
// Fichier: app/Models/Product.php
public function detail(): HasOne { return $this->hasOne(ProductDetail::class); }
// Fichier: app/Models/ProductDetail.php
public function product(): BelongsTo { return $this->belongsTo(Product::class); }
6.6. Relation Plusieurs-à-Plusieurs (`belongsToMany`)
Analogie : Un produit peut être vendu par plusieurs fournisseurs. Nous avons besoin d'une table intermédiaire (pivot) `product_supplier`.
Atelier : Lier les produits et les fournisseurs
1. Créez le modèle `Supplier` et sa migration : `php artisan make:model Supplier -m`
2. Créez la migration pour la table pivot : `php artisan make:migration create_product_supplier_pivot_table`
3. Remplissez les migrations et définissez les relations.
// Migration `create_suppliers_table` -> $table->string('name');
// Migration `create_product_supplier_pivot_table`
Schema::create('product_supplier', function (Blueprint $table) {
$table->primary(['product_id', 'supplier_id']);
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('supplier_id')->constrained()->onDelete('cascade');
});
// Modèle Product.php
public function suppliers(): BelongsToMany { return $this->belongsToMany(Supplier::class); }
// Modèle Supplier.php
public function products(): BelongsToMany { return $this->belongsToMany(Product::class); }
6.7. Relations Polymorphes (Les "Caméléons")
Analogie : Imaginez que vous voulez attacher des images à des produits, mais aussi à des catégories. Une relation polymorphe permet à votre modèle `Image` d'appartenir à n'importe quel autre modèle.
Atelier : Attacher des images aux produits ET aux catégories
1. Créez le modèle `Image` et sa migration : `php artisan make:model Image -m`
2. Dans la migration, utilisez la méthode `morphs()`.
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->string('url');
$table->morphs('imageable'); // Crée `imageable_id` et `imageable_type`
$table->timestamps();
});
3. Définissez les relations.
// Fichier: app/Models/Image.php
public function imageable(): MorphTo { return $this->morphTo(); }
// Fichier: app/Models/Product.php ET app/Models/Category.php
public function images(): MorphMany { return $this->morphMany(Image::class, 'imageable'); }
Atelier Pratique : Afficher les Produits par Catégorie
Nous allons mettre en pratique ces nouveaux concepts en créant une page qui affiche une catégorie et tous les produits qui lui sont associés.
Mise en place de la page de liste des catégories
1. Créer le `CategoryController` et les routes
php artisan make:controller CategoryController --resource
// Fichier: routes/web.php
Route::resource('categories', CategoryController::class);
2. Remplir la méthode `show` du `CategoryController` avec Eager Loading
// Fichier: app/Http/Controllers/CategoryController.php
public function show(Category $category): View
{
$category->load('products');
return view('categories.show', ['category' => $category]);
}
3. Créer la Vue (`resources/views/categories/show.blade.php`)
<h1>Produits de la catégorie : {{ $category->name }}</h1>
<ul>
@forelse ($category->products as $product)
<li><a href="/products/{{ $product->id }}">{{ $product->name }}</a></li>
@empty
<li>Aucun produit dans cette catégorie.</li>
@endforelse
</ul>
Exercice : Afficher les fournisseurs d'un produit
Votre mission est de mettre en pratique la relation Many-to-Many en modifiant la page de détail d'un produit (`/products/{id}`) pour y afficher la liste de ses fournisseurs.
1. Créer et peupler les fournisseurs
Assurez-vous d'avoir les migrations pour `suppliers` et `product_supplier`, ainsi que les modèles et relations `belongsToMany` configurés.
php artisan make:seeder SupplierSeederDans `SupplierSeeder.php`:
use App\Models\Supplier; use App\Models\Product;
public function run(): void
{
$suppliers = Supplier::factory(10)->create();
Product::all()->each(fn ($p) => $p->suppliers()->attach($suppliers->random(rand(1, 3))));
}
Appelez ce seeder dans `DatabaseSeeder.php` et lancez `php artisan migrate:fresh --seed`.
2. Modifiez la méthode `show` du `ProductController`
public function show(Product $product): View
{
$product->load(['category', 'suppliers']);
return view('products.show', ['product' => $product]);
}
3. Mettez à jour la vue `products.show.blade.php`
<h2>Fournisseurs</h2>
<ul>
@forelse ($product->suppliers as $supplier) <li>{{ $supplier->name }}</li>
@empty <li>Aucun fournisseur.</li> @endforelse
</ul>
Ateliers & Exercices Complémentaires
Pour maîtriser pleinement la puissance d'Eloquent, nous allons maintenant mettre en pratique les autres types de relations et de fonctionnalités que nous avons découverts.
Atelier Guidé : Afficher les Détails Techniques (`hasOne`)
Notre table `product_details` est prête, mais elle est vide. Nous allons la peupler et afficher ses informations sur la page de détail du produit.
1. Créer une Factory pour `ProductDetail`
php artisan make:factory ProductDetailFactoryRemplissez la factory (`database/factories/ProductDetailFactory.php`):
public function definition(): array
{
return [
'part_number' => $this->faker->bothify('PN-####-??'),
'weight_kg' => $this->faker->randomFloat(2, 0.1, 25),
];
}
2. Créer un Seeder pour peupler les détails
php artisan make:seeder ProductDetailSeederDans `ProductDetailSeeder.php`, nous allons créer un détail pour chaque produit existant :
use App\Models\Product;
use App\Models\ProductDetail;
public function run(): void
{
Product::all()->each(function ($product) {
ProductDetail::factory()->create(['product_id' => $product->id]);
});
}
N'oubliez pas d'appeler `ProductDetailSeeder::class` dans `DatabaseSeeder.php` et de lancer `php artisan migrate:fresh --seed`.
3. Mettre à jour `ProductController@show` et la Vue
Ajoutez `'detail'` à l'Eager Loading dans le contrôleur : `$product->load(['category', 'suppliers', 'detail']);`
Puis, dans `products/show.blade.php`, ajoutez cette section :
@if($product->detail)
<h2>Détails Techniques</h2>
<ul>
<li>Numéro de pièce : {{ $product->detail->part_number }}</li>
<li>Poids : {{ $product->detail->weight_kg }} kg</li>
</ul>
@endif
Exercice : Formater le Prix avec un Accessor
Actuellement, un prix de `1299.99` s'affiche tel quel. Votre mission est de créer un **attribut virtuel** `formatted_price` sur le modèle `Product` qui retourne le prix formaté en euros avec deux décimales et un séparateur de milliers (ex: "1 299,99 €").
1. Créer l'Accessor dans le modèle `Product`
Ajoutez cette méthode dans `app/Models/Product.php`. Nous créons un nouvel attribut qui n'existe pas en base de données.
use Illuminate\Database\Eloquent\Casts\Attribute;
// ... dans la classe Product
protected function formattedPrice(): Attribute
{
return Attribute::make(
get: fn () => number_format($this->price, 2, ',', ' ') . ' €'
);
}
2. Utiliser le nouvel attribut dans les vues
Modifiez `products/index.blade.php` et `products/show.blade.php`. Remplacez `{{ $product->price }} €` par :
{{ $product->formatted_price }}
Notez que Laravel convertit automatiquement l'appel à la méthode camelCase `formattedPrice()` en un attribut snake_case `formatted_price`. Rafraîchissez vos pages pour voir la nouvelle mise en forme !
Atelier Guidé : Gérer les Images (`Polymorphic`)
Nous allons peupler notre table `images` et afficher les images sur les pages des produits et des catégories.
1. Créer Factory et Seeder pour les Images
php artisan make:factory ImageFactory
php artisan make:seeder ImageSeeder
Remplissez `ImageFactory.php`:
public function definition(): array
{
return [ 'url' => $this->faker->imageUrl(640, 480, 'technics', true) ];
}
Remplissez `ImageSeeder.php` pour attacher des images aux produits ET aux catégories :
use App\Models\Image;
use App\Models\Product;
use App\Models\Category;
public function run(): void
{
// Attache 1 à 3 images à chaque produit
Product::all()->each(function ($product) {
$product->images()->saveMany(Image::factory(rand(1, 3))->make());
});
// Attache 1 image à chaque catégorie
Category::all()->each(function ($category) {
$category->images()->save(Image::factory()->make());
});
}
Appelez `ImageSeeder::class` dans `DatabaseSeeder.php` et lancez `php artisan migrate:fresh --seed`.
2. Mettre à jour les Contrôleurs et les Vues
Dans `ProductController@show`, ajoutez `'images'` à l'Eager Loading : `$product->load(['category', 'suppliers', 'detail', 'images']);`
Dans `products/show.blade.php`, ajoutez :
<h2>Images</h2>
@forelse($product->images as $image)
<img src="{{ $image->url }}" alt="Image du produit" width="200">
@empty
<p>Aucune image pour ce produit.</p>
@endforelse
Faites de même pour `CategoryController@show` et `categories/show.blade.php` pour valider que la relation polymorphe fonctionne des deux côtés !
Partie 3 : Blade, Tailwind & Vite
Chapitre 7 : Blade Avancé - Architecture des Vues
Nous avons créé des vues fonctionnelles, mais elles souffrent d'un défaut majeur : la duplication de code. Chaque fichier contient l'intégralité de la structure HTML. Ce chapitre est dédié à la résolution de ce problème en explorant les deux approches architecturales de Blade : l'héritage de template avec @extends et la composition avec les composants de layout <x-layout>.
Approche 1 : L'Héritage avec @extends, @section et @yield
C'est l'approche historique et la plus simple à comprendre pour organiser ses vues. Elle repose sur un principe d'héritage.
Analogie : Pensez à un document avec un en-tête et un pied de page prédéfinis (le layout). Chaque page que vous créez (la vue enfant) "hérite" de cette structure et se contente de remplir la zone de contenu principal. Le layout est un "cadre photo" rigide ; la vue enfant est la "photo" que l'on place dedans.
Atelier Guidé A : Refactoriser nos vues avec un Layout Principal
Étape 1 : Créer le fichier de layout
Créez le dossier resources/views/layouts, puis le fichier app.blade.php à l'intérieur. Ce fichier contiendra toute notre structure HTML commune.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'Application Commerciale')</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-100">
<header class="bg-white shadow-md p-4">
<h1 class="text-2xl font-bold text-gray-800">GestCom App</h1>
</header>
<div class="container mx-auto p-6">
<main>
@yield('content')
</main>
</div>
<footer class="text-center text-sm text-gray-500 py-4 mt-6">
© {{ date('Y') }} GestCom. Tous droits réservés.
</footer>
</body>
</html>Décryptage : @yield('content') et @yield('title', '...') sont des "trous" que les vues enfants devront remplir. Le second argument de @yield est une valeur par défaut.
Étape 2 : Mettre à jour la vue de la liste des produits
Modifions resources/views/products/index.blade.php pour qu'il hérite de ce layout.
@extends('layouts.app')
@section('title', 'Liste des Produits')
@section('content')
<h1 class="text-3xl font-bold mb-6">Nos Produits</h1>
<div class="bg-white p-6 rounded-lg shadow-md">
<ul class="space-y-4">
@forelse ($products as $product)
<li class="border-b pb-2">
<a href="{{ route('products.show', $product) }}" class="text-lg text-blue-600 hover:underline">
{{ $product->name }}
</a>
<span class="text-gray-600">- {{ $product->formatted_price }}</span>
</li>
@empty
<li class="text-gray-500">Aucun produit trouvé.</li>
@endforelse
</ul>
</div>
@endsectionLe code est maintenant bien plus propre. Si vous rechargez la page /products, elle aura désormais un header et un footer !
Exercice A : Adapter la page de détail d'un produit
Votre mission est d'appliquer exactement la même transformation à la page de détail d'un produit (resources/views/products/show.blade.php) pour qu'elle utilise également notre layout layouts.app.
Solution pour resources/views/products/show.blade.php
@extends('layouts.app')
@section('title', $product->name)
@section('content')
<div class="bg-white p-6 rounded-lg shadow-md">
<a href="{{ route('products.index') }}" class="text-blue-600 hover:underline mb-4 inline-block">← Retour à la liste</a>
<h1 class="text-3xl font-bold mb-2">{{ $product->name }}</h1>
<p class="text-xl text-gray-700 mb-4">{{ $product->formatted_price }}</p>
<div class="prose max-w-none">
{{ $product->description }}
</div>
{{-- ... autres détails ... --}}
</div>
@endsectionApproche 2 : La Composition avec les Composants <x-layout>
C'est l'approche moderne, favorisée par les nouveaux projets Laravel. Elle est basée sur la composition, ce qui la rend plus flexible et modulaire.
Analogie : Pensez à un assemblage de briques LEGO®. Le layout est une grosse brique de base avec des emplacements (`slots`). Vous construisez votre page en y emboîtant d'autres briques (le contenu, un header, une sidebar...). Vous n'héritez de rien, vous composez votre page. C'est plus flexible car vous pouvez facilement changer de "brique de base" (par exemple, avoir un layout pour les invités et un pour les administrateurs).
Atelier Guidé B : Construire un Layout Composé
Étape 1 : Créer les composants du layout
Pour que Laravel les reconnaisse comme des composants, nous les plaçons dans resources/views/components/. Créez un sous-dossier layouts pour l'organisation.
Créez resources/views/components/layouts/sidebar.blade.php :
<aside class="w-64 bg-white shadow-md">
<div class="p-4 font-bold border-b">GestCom App</div>
<nav class="p-4 space-y-2">
<a href="{{ route('products.index') }}" class="block px-4 py-2 text-gray-700 rounded hover:bg-gray-200">Produits</a>
{{-- Plus de liens ici --}}
</nav>
</aside>Étape 2 : Créer le composant de layout principal
Créez resources/views/components/layouts/app.blade.php. Notez l'utilisation de {{ $slot }} au lieu de @yield.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? 'Application Commerciale' }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-100">
<div class="flex h-screen">
<x-layouts.sidebar />
<div class="flex-1 flex flex-col overflow-hidden">
@if(isset($header))
<header class="bg-white shadow p-4">
<h1 class="text-xl font-bold">{{ $header }}</h1>
</header>
@endif
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-200 p-6">
{{ $slot }}
</main>
</div>
</div>
</body>
</html>Étape 3 : Créer une nouvelle page de tableau de bord avec ce layout
Ajoutez une route simple dans routes/web.php : Route::get('/', fn() => view('dashboard'))->name('dashboard');
Créez la vue resources/views/dashboard.blade.php. Observez la nouvelle syntaxe :
<x-layouts.app>
<x-slot:title>
Tableau de Bord
</x-slot:title>
<x-slot:header>
Tableau de Bord
</x-slot:header>
<!-- Le contenu principal va ici, dans le slot par défaut -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-lg font-semibold">Statistique 1</h2>
<p class="text-3xl font-bold mt-2">1,234</p>
</div>
<!-- ... autres cartes ... -->
</div>
</x-layouts.app>Visitez la racine de votre site (/). Vous verrez votre nouveau tableau de bord, construit de manière 100% composable !
Exercice B : Créer une page "Clients" avec le layout composable
Votre mission est de créer une nouvelle page "Clients", accessible à l'URL /clients, en utilisant le composant de layout x-layouts.app.
- Ajoutez une route pour
/clientsqui pointe vers une nouvelle vueclients.index. - Créez la vue
resources/views/clients/index.blade.php. - Utilisez le composant
x-layouts.appcomme squelette. - Passez un titre de page et un titre de header via les slots.
- Ajoutez un contenu simple dans le slot principal.
1. Route dans routes/web.php
Route::get('/clients', fn() => view('clients.index'))->name('clients.index');2. Contenu de resources/views/clients/index.blade.php
<x-layouts.app>
<x-slot:title>Liste des Clients</x-slot:title>
<x-slot:header>Gestion des Clients</x-slot:header>
<div class="bg-white p-6 rounded-lg shadow-md">
<p>La liste des clients sera affichée ici.</p>
</div>
</x-layouts.app>Atelier de Synthèse : Une Modale Interactive dans un Layout Composé
Nous allons maintenant assembler tous ces concepts pour créer une page de "Paramètres" où un bouton ouvrira une modale de confirmation interactive, le tout dans notre architecture par composants.
Étape 1 : Rendre le composant Modal interactif
Nous utiliserons Alpine.js pour l'interactivité. Assurez-vous de l'avoir installé (npm install alpinejs) et importé dans resources/js/app.js (import Alpine from 'alpinejs'; window.Alpine = Alpine; Alpine.start();).
Modifiez resources/views/components/modal.blade.php :
@props(['name'])
<div x-data="{ show: false, name: '{{ $name }}' }"
x-show="show"
x-on:open-modal.window="show = ($event.detail.name === name)"
x-on:close-modal.window="show = false"
x-on:keydown.escape.window="show = false"
style="display: none;"
class="fixed z-50 inset-0 flex items-center justify-center p-4">
<div x-show="show" x-transition.opacity class="fixed inset-0 bg-gray-800 bg-opacity-75"></div>
<div x-show="show" x-transition class="bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900">{{ $title }}</h3>
<div class="mt-2 text-sm text-gray-600">{{ $slot }}</div>
</div>
@if(isset($footer))
<div class="bg-gray-50 px-6 py-4 flex justify-end space-x-3">{{ $footer }}</div>
@endif
</div>
</div>Étape 2 : Créer la page "Paramètres" et le bouton de déclenchement
Ajoutez la route : Route::get('/settings', fn() => view('settings.index'))->name('settings.index');
Ajoutez un lien vers cette page dans votre sidebar (components/layouts/sidebar.blade.php).
Créez la vue resources/views/settings/index.blade.php :
<x-layouts.app>
<x-slot:title>Paramètres</x-slot:title>
<x-slot:header>Paramètres de l'Application</x-slot:header>
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-4">Actions Dangereuses</h2>
<button x-data @click="$dispatch('open-modal', { name: 'confirm-reset' })"
class="px-5 py-2 bg-red-600 text-white font-semibold rounded-lg shadow-md hover:bg-red-700">
Réinitialiser les Données
</button>
</div>
<x-modal name="confirm-reset">
<x-slot:title>Confirmer la réinitialisation</x-slot:title>
Êtes-vous absolument sûr de vouloir réinitialiser toutes les données ? Cette action est irréversible.
<x-slot:footer>
<button @click="$dispatch('close-modal')" class="px-4 py-2 bg-gray-200 rounded-md hover:bg-gray-300">Annuler</button>
<button type="submit" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 ml-2">Oui, Réinitialiser</button>
</x-slot:footer>
</x-modal>
</x-layouts.app>Résultat : Vous avez maintenant une application avec une structure de layout moderne et composable, et une page de paramètres fonctionnelle où un bouton peut déclencher une modale de confirmation interactive. Vous maîtrisez les deux approches de l'architecture des vues dans Laravel.
Conclusion & Perspectives
Contenu non finalisé !
La suite pour très bientôt.
Continuez à pratiquer, car c'est en forgeant que l'on devient forgeron !