Bonnes pratiques Laravel : modèles, contrôleurs, requêtes et sécurité
DOG&DEV · 25/01/2025
Bonnes pratiques Laravel : modèles, contrôleurs, requêtes et sécurité
Adopter les bonnes pratiques Laravel permet de garder un code lisible, maintenable et aligné avec l’écosystème. Ce guide synthétise des recommandations largement partagées : fat models, skinny controllers, principe de responsabilité unique, scopes, FormRequest, Http::pool / batch, transactions, traduction et tests.
Prérequis
- Projet Laravel (10, 11 ou 12)
- Bases en Eloquent, contrôleurs et Blade
1. Fat models, skinny controllers
Déplacer la logique métier et les requêtes dans les modèles (ou des services) pour alléger les contrôleurs.
// ❌ Logique dans le contrôleur
$clients = Client::verified()
->with(['orders' => fn ($q) => $q->where('created_at', '>', now()->subDays(7))])
->get();
// ✅ Méthode ou scope dans le modèle
$clients = $client->getVerifiedWithRecentOrders();
Le contrôleur appelle une méthode claire ; le détail des requêtes et des relations reste dans le modèle.
2. Principe de responsabilité unique (SRP)
Une classe = une responsabilité. Pour la validation, le logging, les mises à jour métier, extraire des FormRequest, Services, Actions ou Logger dédiés.
// ❌ Tout dans le contrôleur
public function update(Request $request) {
$validated = $request->validate([...]);
foreach ($request->tasks as $task) { /* log */ }
$this->project->updateTasks($validated);
return redirect()->route('projects.index');
}
// ✅ Délégation
public function update(UpdateProjectRequest $request, ProjectService $projectService, TaskLogger $logger) {
$logger->logTasks($request->tasks);
$projectService->updateTasks($request->validated());
return redirect()->route('projects.index');
}
3. Relations et création
Utiliser les relations Eloquent plutôt que d’assigner manuellement les clés étrangères :
// ❌
$article->category_id = $category->id;
$article->title = $request->input('title');
$article->save();
// ✅
$category->articles()->create($request->safe()->only(['title', 'content', 'verified']));
4. Transactions pour les opérations atomiques
Tout ce qui touche plusieurs écritures liées doit être dans une transaction :
DB::transaction(function () use ($request) {
$order = Order::create($request->validated());
Payment::create(['order_id' => $order->id]);
});
5. Eager loading pour éviter le N+1
Ne pas lancer de requêtes dans les boucles Blade. Charger les relations en amont :
// ❌ N+1
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
// ✅
$users = User::with('profile')->get();
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
6. Chunk pour les gros volumes
Pour des milliers d’enregistrements, utiliser chunk() (ou chunkById) pour limiter la mémoire :
User::chunk(500, function ($users) {
foreach ($users as $user) {
// traitement
}
});
7. Constantes au lieu de valeurs en dur
Remplacer les magic strings par des constantes ou des enums :
// ❌
return $user->type === 'admin';
// ✅
return $user->type === UserType::ADMIN;
8. Traduction des chaînes
Préparer l’i18n dès le début avec __() :
return back()->with('message', __('Votre article a bien été ajouté.'));
9. Injection de dépendances
Préférer l’injection (constructeur ou méthode) à new pour les services :
public function __construct(protected UserService $userService) {}
public function store(StoreUserRequest $request) {
$this->userService->create($request->validated());
}
10. Configuration au lieu de env() direct
Lire la config via config() plutôt qu’env() dans le code applicatif :
// ❌
$apiKey = env('API_KEY');
// ✅ config/services.php : 'api_key' => env('API_KEY'),
$apiKey = config('services.api_key');
11. Dates en datetime / date
Caster les colonnes de date pour manipuler des objets Carbon :
protected $casts = ['ordered_at' => 'datetime'];
// Blade : {{ $object->ordered_at->format('d/m/Y') }}
12. Http::pool et Http::batch
Pour plusieurs appels API indépendants, utiliser Http::pool (ou Http::batch avec callbacks) au lieu d’appels séquentiels :
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('github')->get('https://api.github.com/users/laravel'),
$pool->as('weather')->get('https://api.weather.com/v3/today'),
]);
13. Documenter avec parcimonie
Éviter les blocs de commentaires massifs. Préférer des noms explicites (méthodes, variables) ; réserver les commentaires aux règles métier ou cas complexes.
14. Tests
Couvrir au minimum les routes critiques, les FormRequest et les services ; les tests servent de sécurité pour le refactoring et la régression.
Dépannage
| Symptôme | Piste | Correctif |
|---|---|---|
| N+1, requêtes excessives | Boucles sur des relations non chargées | with(), withCount() et analyse des requêtes (Debugbar, Telescope) |
| Contrôleur surchargé | Trop de responsabilités | Extraire en Service, Action, FormRequest |
| Données incohérentes | Plusieurs écritures sans transaction | Envelopper dans DB::transaction() |
Bonnes pratiques
- FormRequest pour toute entrée utilisateur ; Policy / Gate pour l’autorisation.
- Scopes (locaux et globaux) pour réutiliser des contraintes de requêtes.
- Laravel Pint pour uniformiser le style de code en équipe.
Ressources
Cet article s’inscrit dans notre série de guides technique et développement web. Pour un serveur ou une application sur-mesure, contact.