Aujourd’hui, j’ai franchi une étape importante dans mon apprentissage de Laravel : comprendre réellement le fonctionnement des migrations. Pas juste copier-coller du code, mais comprendre la logique derrière chaque ligne, chaque choix technique.
En construisant la base de données de BeeNote (mon app de suivi de ruchers), j’ai découvert que les migrations ne sont pas qu’un outil technique - elles racontent l’histoire de ton application et de ta logique métier.
Le contexte : Laravel 12 et VILT Stack
Pour ce projet, j’utilise :
- Laravel 12.22.1 (framework backend)
- Vue.js 3 (frontend)
- Inertia.js (pont entre Laravel et Vue)
- Tailwind CSS (styling)
- PostgreSQL (base de données)
L’objectif : maîtriser cette stack pour devenir freelance spécialisé en développement pour la GreenTech.
Les migrations système : Comprendre la base Laravel
1. Migration des utilisateurs
Première leçon : Laravel ne crée pas qu’une table users
, mais 3 tables interconnectées :
// Table users avec Jetstream
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->foreignId('current_team_id')->nullable(); // Pour les équipes
$table->string('profile_photo_path', 2048)->nullable();
// Table password_reset_tokens
$table->string('email')->primary();
$table->string('token');
// Table sessions
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->text('user_agent')->nullable();
Insight : Laravel pense sécurité dès le départ. Les tokens de reset, la gestion des sessions, l’authentification à deux facteurs - tout est prévu.
2. Le système de cache
// Table cache
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
// Table cache_locks
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
Découverte importante : Pas besoin de Redis pour débuter ! Laravel peut utiliser PostgreSQL comme système de cache. Les cache_locks
évitent les “cache stampedes” - quand 100 requêtes simultanées veulent calculer la même donnée.
❌ Sans cache (calcul à chaque fois) :
// Dans ton Controller - CHAQUE visite de page
public function showRucher($id) {
// Ces calculs se font à CHAQUE fois (lent sur gros datasets)
$totalRuches = Ruche::where('rucher_id', $id)->count();
$productionTotale = Recolte::where('rucher_id', $id)->sum('quantite');
$moyenneProduction = $productionTotale / $totalRuches;
// 50ms de calculs à chaque fois...
}
✅ Avec cache database :
public function showRucher($id) {
$stats = Cache::remember("rucher_stats_{$id}", 3600, function() use ($id) {
// Ce code ne s'exécute QUE si pas en cache
return [
'total_ruches' => Ruche::where('rucher_id', $id)->count(),
'production' => Recolte::where('rucher_id', $id)->sum('quantite'),
];
});
// 1ms de lecture cache les fois suivantes !
}
3. Le système de queues
Les queues Laravel résolvent un problème concret : éviter que l’utilisateur attende.
❌ Sans queues (synchrone) :
// L'utilisateur upload une photo de 5MB de sa ruche
public function uploadPhotoRuche(Request $request) {
$photo = $request->file('photo');
// Pendant que l'utilisateur ATTEND (3-5 secondes)...
$optimized = $this->optimizeImage($photo); // 2 secondes
$thumbnail = $this->createThumbnail($photo); // 1 seconde
$this->saveToStorage($optimized); // 1 seconde
$this->updateDatabase($optimized); // 0.5 seconde
return response()->json(['success' => true]);
// ↑ L'utilisateur attend 4.5 secondes sans rien pouvoir faire
}
✅ Avec queues (asynchrone) :
// Upload instantané
public function uploadPhotoRuche(Request $request) {
$photo = $request->file('photo');
// Sauvegarde temporaire (0.1 seconde)
$tempPath = $photo->store('temp');
// Met le job en queue (0.1 seconde)
ProcessRuchePhoto::dispatch($tempPath, auth()->id());
return response()->json(['success' => true, 'processing' => true]);
// ↑ L'utilisateur a sa réponse en 0.2 seconde !
}
// Job qui s'exécute en arrière-plan
class ProcessRuchePhoto implements ShouldQueue {
public function handle() {
// Le traitement lourd se fait maintenant, sans bloquer l'utilisateur
$optimized = $this->optimizeImage();
// etc...
}
}
Les migrations métier : Traduire l’apiculture en base de données
1. Ruchers : La géolocalisation précise
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('nom', 100);
$table->decimal('latitude', 10, 8)->nullable(); // 47.12345678
$table->decimal('longitude', 11, 8)->nullable(); // -1.23456789
Leçon : Utiliser decimal
pour les coordonnées GPS, pas float
. La précision compte quand on localise des ruchers !
2. Ruches : Les ENUMs pour standardiser
$table->enum('type', ['dadant', 'warre', 'langstroth', 'ktbh', 'autre'])->default('dadant');
$table->enum('statut', ['active', 'inactive', 'morte'])->default('active');
// Découverte importante : standardisation du marquage des reines
$table->enum('couleur_marquage_reine', [
'blanc', 'jaune', 'rouge', 'vert', 'bleu'
])->nullable(); // Code couleur international !
Insight métier : Le marquage des reines suit un code couleur international standardisé. Blanc/argent pour les années finissant par 1 ou 6, jaune/or pour 2 ou 7, etc. Un ENUM évite les erreurs de saisie.
3. Visites : La complexité du métier apicole
La table visites
fait 47 colonnes ! Pourquoi ? Parce que l’apiculture est complexe :
// Météo & conditions
$table->enum('meteo', ['ensoleille', 'nuageux', 'pluvieux', 'orageux'])->nullable();
$table->decimal('temperature', 4, 1)->nullable();
// État de la colonie
$table->enum('humeur_colonie', ['non_observe', 'calme', 'agitee', 'agressive', 'tres_agressive'])
->default('non_observe');
// Observations reine
$table->enum('reine_vue', ['non_observe', 'ok', 'non_ok'])->default('non_observe');
$table->enum('ponte_observee', ['non_observe', 'ok', 'non_ok'])->default('non_observe');
// Santé & parasites
$table->enum('varroas_observes', ['non_observe', 'ok', 'non_ok'])->default('non_observe');
$table->integer('nombre_varroas_estimes')->nullable();
Philosophie : Tous les champs avec default('non_observe')
. Pas besoin de tout remplir à chaque visite, mais toutes les possibilités sont disponibles.
4. Photos : La magie du polymorphisme
💡 Rappel polymorphisme : Une relation polymorphique permet à un modèle (ici
Photo
) d’appartenir à plusieurs types d’entités différentes avec une seule table. Au lieu de créeruser_photos
,visite_photos
,ruche_photos
… on a UNE tablephotos
qui peut se lier à n’importe quel modèle !
$table->morphs('photoable'); // Crée photoable_type + photoable_id
$table->string('chemin', 500);
$table->string('mime_type', 50);
$table->unsignedInteger('ordre')->default(0);
Révélation : Une seule table photos
pour TOUT ! Photos de ruches, de visites, de traitements, de récoltes, même de profils utilisateur.
// Photo d'une visite
photoable_type = 'App\Models\Visite'
photoable_id = 42
// Photo d'une ruche
photoable_type = 'App\Models\Ruche'
photoable_id = 15
Performance : Comprendre les index
L’analogie du dictionnaire
❌ Sans index (comme un livre normal) :
-- Pour trouver "Mes tâches à faire" dans une table de 10 000 lignes
SELECT * FROM taches WHERE user_id = 123 AND statut = 'a_faire';
-- MySQL doit scanner TOUTE la table ligne par ligne
-- Temps d'exécution : 50ms
-- EXPLAIN: Full Table Scan (LENT)
✅ Avec index (comme un dictionnaire) :
// Dans la migration
$table->index(['user_id', 'statut']); // Index composé
// MySQL crée une "table de raccourcis"
// user_id=123, statut=a_faire → lignes 45, 1547, 8932
// user_id=123, statut=terminee → lignes 12, 445, 2156
-- Même requête avec index
SELECT * FROM taches WHERE user_id = 123 AND statut = 'a_faire';
-- MySQL va DIRECTEMENT aux bonnes lignes
-- Temps d'exécution : 2ms
-- EXPLAIN: Index Range Scan (RAPIDE)
Impact réel : 50ms → 2ms sur des tables de 10k+ enregistrements.
Attention aux doublons !
❌ Erreur classique avec PostgreSQL :
Schema::create('photos', function (Blueprint $table) {
$table->morphs('photoable'); // Crée AUTOMATIQUEMENT l'index
$table->index(['photoable_type', 'photoable_id']); // DOUBLON !
// → ERREUR: la relation « photos_photoable_type_photoable_id_index » existe déjà
});
✅ Code correct :
Schema::create('photos', function (Blueprint $table) {
$table->morphs('photoable'); // Index créé automatiquement
$table->index('ordre'); // Seulement les index supplémentaires
});
Leçon : Laravel est intelligent - foreignId()->constrained()
et morphs()
créent leurs index automatiquement.
Les relations complexes : Flexibilité et traçabilité
Table tâches : Le cœur de la planification
// Relations optionnelles - flexibilité maximale
$table->foreignId('rucher_id')->nullable()->constrained()->onDelete('cascade');
$table->foreignId('ruche_id')->nullable()->constrained()->onDelete('cascade');
// Récurrence pour tâches répétitives
$table->boolean('recurrente')->default(false);
$table->enum('periode_recurrence', ['hebdomadaire', 'mensuelle', 'trimestrielle', 'saisonniere', 'annuelle'])->nullable();
// Traçabilité avec set null
$table->foreignId('visite_id')->nullable()->constrained()->onDelete('set null');
Logique : Une tâche peut être générale (niveau rucher), spécifique (niveau ruche), ou administrative. Si une visite est supprimée, la tâche garde son historique mais perd le lien.
Outils de debug essentiels
# Créer une migration
php artisan make:migration create_ruchers_table
# Exécuter
php artisan migrate
# Rollback précis (sauveur !)
php artisan migrate:rollback --step=1
# Vérifier l'état
php artisan migrate:status
Ce que j’ai appris sur moi-même
-
Je sous-estimais la complexité métier : 47 colonnes pour les visites, ce n’est pas excessif, c’est la réalité de l’apiculture.
-
Les standards existent partout : Couleurs de marquage des reines, types de miels, méthodes de traitement… Il faut creuser son domaine.
-
Performance dès le départ : Penser index dès la conception, pas après.
-
Laravel est intelligent : Beaucoup d’optimisations automatiques si on utilise les bonnes méthodes.
Prochaines étapes
Maintenant que la structure de données est solide, direction :
- Models Eloquent avec toutes les relations
- Seeders pour peupler avec des données réalistes
- Controllers et première logique métier
- Interface Vue.js avec Inertia
Ressources qui m’ont aidé
- Documentation Laravel Migrations
- Eloquent Relationships
- Ma propre expérience d’apiculteur pour la logique métier
Cet article fait partie de ma série “Learning Laravel in Public” où je documente mon apprentissage pour devenir freelance spécialisé GreenTech. Suivez mon progrès sur X/Twitter !
Questions, remarques, ou envie de discuter apiculture ET code ? Les commentaires sont ouverts !