TP “E-commerce” - Étape 5
ORM, modèles et migrations avec Laravel 12
Objectifs pédagogiques
Prérequis
Création et configuration de la base de données
Introduction aux migrations dans Laravel 12
Configuration du modèle Product
Modification du contrôleur ProductController
Mission 1: Importation des données existantes
Mission 2: Vérification du fonctionnement de l’application
Mission 3: Création du formulaire d’édition de produit
Mission 4: Ajout de la fonctionnalité de suppression
Annexe : Guide Eloquent ORM
Conclusion
TP “E-commerce” - Étape 5
ORM, modèles et migrations avec Laravel 12
Objectifs pédagogiques
Comprendre le fonctionnement des modèles Eloquent dans Laravel 12
Apprendre à créer et exécuter des migrations
Manipuler des enregistrements dans la base de données via un ORM
Gérer l’upload de fichiers de manière sécurisée
Implémenter un CRUD complet pour les produits
Prérequis
Avoir terminé les étapes 1 à 4 du TP “E-commerce”
Auteur: Steeve LEFORT <[email protected]> page 1 sur 21
Disposer d’un environnement de développement fonctionnel avec Laravel 12
Comprendre les bases du framework Laravel (routes, vues, contrôleurs)
Création et configuration de la base de données
1. Comprendre les différentes options de base de données
Laravel 12 supporte plusieurs systèmes de base de données :
MySQL 8.0+ (recommandé pour la production)
PostgreSQL 14.0+
SQLite 3.8.8+ (parfait pour le développement)
SQL Server 2017+
Pour ce TP, nous utiliserons SQLite par défaut pour sa simplicité, mais vous pouvez utiliser MySQL si
vous préférez.
2. Configuration avec SQLite (recommandé)
1. Assurez-vous que votre fichier .env est configuré correctement :
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
2. Créez un fichier de base de données vide :
touch database/database.sqlite
3. Configuration avec MySQL (optionnel)
Si vous préférez utiliser MySQL :
1. Modifiez votre fichier .env :
Auteur: Steeve LEFORT <[email protected]> page 2 sur 21
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_ecommerce
DB_USERNAME=root # À adapter selon votre configuration
DB_PASSWORD= # À adapter selon votre configuration
2. Créez la base de données dans MySQL via Laragon ou phpMyAdmin.
Introduction aux migrations dans Laravel 12
Les migrations dans Laravel sont comme un système de versioning pour votre base de données. Elles
permettent de :
Définir la structure de votre base de données en PHP
Partager cette structure avec votre équipe
Faciliter le déploiement sur différents environnements
Garder un historique des modifications
1. Création du modèle Product avec sa migration
Créons un modèle Product avec sa migration associée :
php artisan make:model Product -m
Cette commande génère :
Un modèle : app/Models/Product.php
Une migration :
database/migrations/XXXX_XX_XX_XXXXXX_create_products_table.php
2. Modification de la migration pour la table products
Ouvrez le fichier de migration créé et modifiez la méthode up() :
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
Auteur: Steeve LEFORT <[email protected]> page 3 sur 21
$table->id(); // Identifiant unique et auto-
incrémenté
$table->string('name'); // Nom du produit
$table->string('image')->nullable(); // Chemin de l'image, peut être
null
$table->decimal('price', 10, 2); // Prix avec 10 chiffres dont 2
décimales
$table->integer('vat'); // Taux de TVA (%)
$table->text('description'); // Description du produit
$table->timestamps(); // Colonnes created_at et
updated_at
});
}
3. Exécution de la migration
Lancez la migration pour créer la table dans la base de données :
php artisan migrate
Si vous souhaitez voir l’état des migrations :
php artisan migrate:status
Configuration du modèle Product
Dans Laravel, un modèle représente une table de la base de données. Il permet d’interagir avec cette table
sans écrire de SQL.
1. Modification du modèle
Ouvrez app/Models/Product.php et ajoutez l’attribut $fillable pour définir les champs qui
peuvent être assignés en masse :
<?php
namespace App\Models;
Auteur: Steeve LEFORT <[email protected]> page 4 sur 21
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
/**
* Les attributs qui sont assignables en masse.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'price',
'vat',
'description',
'image'
];
}
2. Comprendre l’assignation de masse et la protection contre les vulnérabilités
L’attribut $fillable est important pour la sécurité :
Il spécifie quels champs peuvent être remplis via create() ou update()
Il protège contre les attaques de type “mass assignment”
Sans lui, un attaquant pourrait injecter des champs non prévus dans vos formulaires
Note importante : Une attaque par “mass assignment” se produit lorsqu’un utilisateur malveillant
ajoute des paramètres supplémentaires dans une requête HTTP. Par exemple, si votre modèle a un
champ ’isadmin’ qui n’est pas protégé et qu’un utilisateur ajoute is_admin=1 dans sa requête, il
pourrait s’attribuer des droits d’administrateur. L’attribut $fillable empêche ce type d’attaque en
limitant les champs qui peuvent être modifiés en masse.
Modification du contrôleur ProductController
Auteur: Steeve LEFORT <[email protected]> page 5 sur 21
Maintenant que nous avons notre modèle et notre table, adaptons notre contrôleur pour utiliser la base de
données au lieu des données statiques.
1. Importation du modèle
Ajoutez cette ligne en haut de votre ProductController.php :
use App\Models\Product;
2. Adaptation de la méthode store pour enregistrer en base de données
La méthode store doit maintenant créer une entrée dans la base de données :
/**
* Enregistre un nouveau produit
*/
public function store(ProductRequest $request)
{
// Validation déjà gérée par ProductRequest
// Créer un nouveau produit avec les données validées
$product = Product::create($request->except('image'));
// Gérer l'upload de l'image si elle existe
if ($request->hasFile('image') && $request->file('image')->isValid()) {
$image = $product->id . '.' . $request->image->extension();
$request->file('image')->move(public_path('images'), $image);
$product->image = $image;
$product->save();
} else {
// Utiliser une image par défaut
$product->image = 'placeholder.jpg';
$product->save();
}
return redirect('/')->with('success', 'Produit ajouté avec succès!');
}
Auteur: Steeve LEFORT <[email protected]> page 6 sur 21
Explication :
Product::create() crée une nouvelle entrée dans la table products
$request->except('image') prend toutes les données validées sauf l’image
public_path('images') renvoie le chemin complet vers le dossier public/images
La méthode move() déplace le fichier téléchargé vers sa destination finale
Nous utilisons l’ID du produit comme nom de fichier pour éviter les conflits
3. Modification de la méthode viewAll pour afficher les produits de la base de données
Modifiez la méthode qui affiche tous les produits :
/**
* Affiche tous les produits
*/
public function viewAll()
{
// Récupérer tous les produits de la base de données
$products = Product::all();
return view('products', ['products' => $products]);
}
4. Modification de la méthode viewProduct pour afficher un produit spécifique
/**
* Affiche les détails d'un produit
*/
public function viewProduct($id)
{
try {
$product = Product::findOrFail($id);
return view('product_details', ['product' => $product]);
} catch (\Exception $e) {
return view('error')->with('error', 'Produit non trouvé');
}
}
Auteur: Steeve LEFORT <[email protected]> page 7 sur 21
5. Adaptation de la méthode addToCart
/**
* Ajoute un produit au panier
*/
public function addToCart($id)
{
try {
// Vérifier que le produit existe
$product = Product::findOrFail($id);
// Récupérer le panier actuel ou créer un tableau vide
$cart = session('cart', []);
// Ajouter l'ID du produit au panier
$cart[] = $id;
// Sauvegarder le panier en session
session(['cart' => $cart]);
return redirect('/cart')->with('success', 'Produit ajouté au
panier');
} catch (\Exception $e) {
return redirect('/')->with('error', 'Produit introuvable');
}
}
6. Adaptation de la méthode viewCart
/**
* Affiche le contenu du panier
*/
public function viewCart()
{
// Récupérer le panier depuis la session
$cart = session('cart', []);
// Récupérer les produits correspondants aux IDs dans le panier
Auteur: Steeve LEFORT <[email protected]> page 8 sur 21
$cartItems = [];
$totalHT = 0;
$totalTVA = 0;
$totalTTC = 0;
foreach ($cart as $id) {
try {
$product = Product::findOrFail($id);
$cartItems[] = $product;
// Calculs
$totalHT += $product->price;
$tva = $product->price * ($product->vat / 100);
$totalTVA += $tva;
$totalTTC += $product->price + $tva;
} catch (\Exception $e) {
// Ignorer les produits qui n'existent plus
}
}
return view('cart', [
'cartItems' => $cartItems,
'totalHT' => $totalHT,
'totalTVA' => $totalTVA,
'totalTTC' => $totalTTC
]);
}
Mission 1: Importation des données existantes
Avant de tester notre application avec la base de données, importons les produits existants.
1. Création d’un seeder pour les produits
Laravel propose un mécanisme de “seeding” pour remplir la base de données avec des données de
test.
php artisan make:seeder ProductSeeder
Auteur: Steeve LEFORT <[email protected]> page 9 sur 21
Modifiez le fichier database/seeders/ProductSeeder.php :
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Product;
use App\Models\ProductManager;
class ProductSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Récupérer les produits depuis ProductManager
$staticProducts = ProductManager::getAllProducts();
// Les insérer dans la base de données
foreach ($staticProducts as $product) {
Product::create([
'name' => $product->name,
'image' => $product->image,
'price' => $product->price,
'vat' => $product->vat,
'description' => $product->description,
]);
}
$this->command->info('Produits importés avec succès !');
}
}
Explication: Un “seeder” est une classe qui permet de peupler votre base de données avec des
données de test ou initiales. C’est très utile pour:
Auteur: Steeve LEFORT <[email protected]> page 10 sur 21
Disposer rapidement d’un jeu de données pour développer et tester votre application
Préparer des données de démonstration
Initialiser votre base de données avec des données essentielles au fonctionnement de
l’application
2. Enregistrement du seeder
Modifiez database/seeders/DatabaseSeeder.php pour inclure notre seeder :
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
ProductSeeder::class,
]);
}
}
3. Exécution du seeder
php artisan db:seed
Ou pour ne lancer que le seeder de produits :
php artisan db:seed --class=ProductSeeder
Mission 2: Vérification du fonctionnement de l’application
Auteur: Steeve LEFORT <[email protected]> page 11 sur 21
Démarrez l’application et testez toutes les fonctionnalités :
composer run dev
Vérifiez les points suivants :
La page d’accueil affiche bien les produits de la base de données
La page de détail d’un produit fonctionne correctement
Le formulaire d’ajout de produit permet d’ajouter un nouveau produit en base de données
L’ajout au panier fonctionne comme avant
Si vous rencontrez des problèmes, n’hésitez pas à vérifier les logs d’erreur dans le fichier
storage/logs/laravel.log.
Mission 3: Création du formulaire d’édition de produit
Maintenant, ajoutons la fonctionnalité d’édition de produit.
1. Ajout des routes pour l’édition
Dans routes/web.php, ajoutez :
// Affichage du formulaire d'édition
Route::get('/product/edit/{id}', [ProductController::class, 'edit']);
// Traitement du formulaire d'édition
Route::post('/product/edit/{id}', [ProductController::class, 'update']);
2. Méthodes du contrôleur pour l’édition
Ajoutez ces méthodes à votre ProductController :
/**
* Affiche le formulaire d'édition d'un produit
*/
public function edit($id)
{
$product = Product::findOrFail($id);
Auteur: Steeve LEFORT <[email protected]> page 12 sur 21
return view('product.edit', ['product' => $product]);
}
/**
* Met à jour un produit existant
*/
public function update(ProductRequest $request, $id)
{
// Récupérer le produit
$product = Product::findOrFail($id);
// Mettre à jour les champs texte
$product->update($request->except('image'));
// Traiter la nouvelle image si fournie
if ($request->hasFile('image') && $request->file('image')->isValid()) {
// Supprimer l'ancienne image si ce n'est pas l'image par défaut
if ($product->image && $product->image != 'placeholder.jpg' &&
file_exists(public_path('images/' . $product->image))) {
unlink(public_path('images/' . $product->image));
}
// Enregistrer la nouvelle image
$image = $product->id . '.' . $request->image->extension();
$request->file('image')->move(public_path('images'), $image);
$product->image = $image;
$product->save();
}
return redirect('/')->with('success', 'Produit mis à jour avec succès');
}
Explication :
La fonction unlink() est une fonction PHP native qui supprime un fichier
Nous vérifions l’existence du fichier avec file_exists() avant de tenter de le supprimer pour
éviter les erreurs
Nous préservons l’image par défaut en vérifiant que ce n’est pas ’placeholder.jpg’
Auteur: Steeve LEFORT <[email protected]> page 13 sur 21
3. Création de la vue pour l’édition
Créez le fichier resources/views/product/edit.blade.php :
@extends('layouts.master')
@section('title', 'Modifier le produit')
@section('content')
<div class="container">
<h1>Modifier le produit: {{ $product->name }}</h1>
@if ($errors->any())
<div class="alert-error">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ url('/product/edit/' . $product->id) }}" method="post"
enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="name">Nom du produit:</label>
<input type="text" id="name" name="name" value="{{ old('name',
$product->name) }}" required>
</div>
<div class="form-group">
<label for="price">Prix (HT):</label>
<input type="number" id="price" name="price" value="{{
old('price', $product->price) }}" step="0.01" min="0" required>
</div>
Auteur: Steeve LEFORT <[email protected]> page 14 sur 21
<div class="form-group">
<label for="vat">TVA (%):</label>
<input type="number" id="vat" name="vat" value="{{ old('vat',
$product->vat) }}" min="0" max="100" required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea id="description" name="description" rows="5" required>
{{ old('description', $product->description) }}</textarea>
</div>
<div class="form-group">
<label>Image actuelle:</label>
<img src="{{ asset('images/' . $product->image) }}" alt="{{
$product->name }}" style="max-width: 200px;">
</div>
<div class="form-group">
<label for="image">Nouvelle image (laisser vide pour conserver
l'image actuelle):</label>
<input type="file" id="image" name="image">
</div>
<button type="submit" class="btn-primary">Mettre à jour</button>
</form>
</div>
@endsection
4. Ajout des liens d’édition
Modifiez la vue products.blade.php pour ajouter des liens vers le formulaire d’édition :
@foreach($products as $product)
<div class="product-card">
<!-- Contenu existant -->
<div class="product-actions">
Auteur: Steeve LEFORT <[email protected]> page 15 sur 21
<a href="{{ url('/details/' . $product->id) }}" class="btn-
primary">Voir</a>
<a href="{{ url('/product/edit/' . $product->id) }}" class="btn-
secondary">Modifier</a>
</div>
</div>
@endforeach
Mission 4: Ajout de la fonctionnalité de suppression
Pour compléter notre CRUD (Create, Read, Update, Delete), ajoutons la possibilité de supprimer un
produit.
1. Ajout de la route pour la suppression
Dans routes/web.php, ajoutez :
// Suppression d'un produit
Route::get('/product/delete/{id}', [ProductController::class, 'delete']);
2. Méthode du contrôleur pour la suppression
Ajoutez cette méthode à votre ProductController :
/**
* Supprime un produit
*/
public function delete($id)
{
try {
// Récupérer le produit
$product = Product::findOrFail($id);
// Supprimer l'image associée (sauf si c'est l'image par défaut)
if ($product->image && $product->image != 'placeholder.jpg' &&
file_exists(public_path('images/' . $product->image))) {
unlink(public_path('images/' . $product->image));
}
Auteur: Steeve LEFORT <[email protected]> page 16 sur 21
// Supprimer le produit
$product->delete();
return redirect('/')->with('success', 'Produit supprimé avec
succès');
} catch (\Exception $e) {
return redirect('/')->with('error', 'Erreur lors de la suppression du
produit: ' . $e->getMessage());
}
}
3. Ajout du bouton de suppression dans la liste des produits
Modifiez à nouveau la vue products.blade.php pour ajouter un lien de suppression:
<div class="product-actions">
<a href="{{ url('/details/' . $product->id) }}" class="btn-
primary">Voir</a>
<a href="{{ url('/product/edit/' . $product->id) }}" class="btn-
secondary">Modifier</a>
<a href="{{ url('/product/delete/' . $product->id) }}" class="btn-danger"
onclick="return confirm('Êtes-vous sûr de vouloir supprimer ce
produit?');">Supprimer</a>
</div>
Explication : La fonction JavaScript confirm() affiche une boîte de dialogue avec un choix Oui/Non.
Si l’utilisateur clique sur “Annuler”, l’événement de clic est annulé et la suppression n’a pas lieu. C’est
une façon simple d’ajouter une confirmation avant suppression.
Précision: Une suppression est généralement associée au verbe HTTP `DELETE`. Cela nécessiterait
de créer autant de formulaires que de boutons de suppression. Pour simplifier le code, nous avons fait
le choix d’utiliser le verbe `GET`.
4. Ajout de styles CSS pour le bouton de suppression
Dans votre fichier public/css/styles.css, ajoutez un style pour le bouton de suppression :
Auteur: Steeve LEFORT <[email protected]> page 17 sur 21
.btn-danger {
background-color: #dc3545;
color: white;
padding: 8px 15px;
border: none;
border-radius: 4px;
text-decoration: none;
display: inline-block;
}
.btn-danger:hover {
background-color: #c82333;
}
.btn-secondary {
background-color: #6c757d;
color: white;
padding: 8px 15px;
border: none;
border-radius: 4px;
text-decoration: none;
display: inline-block;
}
.btn-secondary:hover {
background-color: #5a6268;
}
Annexe : Guide Eloquent ORM
Eloquent est l’ORM de Laravel. Il vous permet de travailler avec votre base de données comme si vous
manipuliez des objets PHP, sans écrire de SQL.
1. Récupération de données
// Tous les produits
$products = Product::all();
Auteur: Steeve LEFORT <[email protected]> page 18 sur 21
// Un produit spécifique par ID
$product = Product::find(1);
// Un produit avec erreur 404 si non trouvé
$product = Product::findOrFail(1);
// Le premier produit qui correspond à un critère
$cheapProduct = Product::where('price', '<', 50)->first();
// Tous les produits qui correspondent à un critère
$expensiveProducts = Product::where('price', '>', 100)->get();
// Chaînage de conditions
$products = Product::where('price', '>', 50)
->where('vat', 20)
->orderBy('name')
->get();
// Pagination
$paginatedProducts = Product::paginate(10);
2. Création de données
// Méthode 1: via create (assignation en masse)
$product = Product::create([
'name' => 'Nouveau produit',
'price' => 99.99,
'vat' => 20,
'description' => 'Description du produit',
'image' => 'default.jpg'
]);
// Méthode 2: via new et save
$product = new Product();
$product->name = 'Nouveau produit';
$product->price = 99.99;
$product->vat = 20;
Auteur: Steeve LEFORT <[email protected]> page 19 sur 21
$product->description = 'Description du produit';
$product->image = 'default.jpg';
$product->save();
3. Mise à jour de données
// Méthode 1: trouver puis modifier
$product = Product::find(1);
$product->price = 129.99;
$product->save();
// Méthode 2: mise à jour via update (assignation en masse)
$product->update([
'price' => 129.99,
'description' => 'Nouvelle description'
]);
// Méthode 3: mise à jour en masse de plusieurs enregistrements
Product::where('vat', 19)->update(['vat' => 20]);
4. Suppression de données
// Méthode 1: trouver puis supprimer
$product = Product::find(1);
$product->delete();
// Méthode 2: suppression directe
Product::destroy(1);
// Suppression multiple
Product::destroy([1, 2, 3]);
// Suppression conditionnelle
Product::where('price', '<', 10)->delete();
5. Requêtes avancées
Auteur: Steeve LEFORT <[email protected]> page 20 sur 21
// Comptage
$count = Product::where('price', '>', 100)->count();
// Moyenne
$avgPrice = Product::avg('price');
// Somme
$totalValue = Product::sum('price');
// Min/Max
$cheapest = Product::min('price');
$mostExpensive = Product::max('price');
// Combinaison de méthodes
$stats = Product::where('vat', 20)
->selectRaw('COUNT(*) as count, AVG(price) as avg_price')
->first();
Conclusion
Dans cette étape du TP, vous avez appris à :
Configurer une base de données dans Laravel 12
Créer des migrations et des modèles
Manipuler des données avec Eloquent ORM
Implémenter un CRUD complet (Create, Read, Update, Delete)
Gérer l’upload de fichiers
Ces compétences sont essentielles pour tout développeur Laravel et vous permettront de créer des
applications web robustes et évolutives.
Dans les prochaines étapes du TP, nous aborderons l’authentification avec Laravel Breeze, les middlewares
et les API REST.
Pour approfondir :
Documentation officielle Eloquent : https://laravel.com/docs/12.x/eloquent
Documentation sur les migrations : https://laravel.com/docs/12.x/migrations
Auteur: Steeve LEFORT <[email protected]> page 21 sur 21