Qu’est-ce que Laravel ?
Laravel est un framework PHP full-stack conçu autour d’une promesse simple : rendre le développement web agréable sans sacrifier la puissance. Il suit le pattern MVC, mais l’architecture importe moins que l’expérience d’utilisation. Le routing est expressif, la couche de base de données se lit comme de l’anglais, et les composants que vous devriez normalement assembler à partir d’une douzaine de bibliothèques sont déjà intégrés.
Créé par Taylor Otwell en 2011, il est devenu depuis le framework PHP le plus populaire, avec un écosystème qui s’étend bien au-delà du framework lui-même : Forge et Vapor pour le déploiement, Nova pour les panels d’administration, Livewire pour les interfaces réactives et Sanctum pour l’authentification.
Si vous avez déjà écrit du PHP à la main, utiliser Laravel donne l’impression que quelqu’un a enfin organisé la boîte à outils.
Routage et contrôleurs
Chaque requête passe par une route. Les routes web se trouvent dans routes/web.php et les routes API dans routes/api.php, et toutes deux utilisent la même syntaxe expressive.
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::post('/posts', [PostController::class, 'store']);
Route::apiResource('posts', PostController::class);
Route::apiResource génère en une seule ligne l’ensemble conventionnel des routes index, store, show, update et destroy. Un contrôleur contient ensuite une méthode par action, ce qui permet de garder chaque gestionnaire léger.
class PostController extends Controller
{
public function index()
{
return Post::latest()->paginate(20);
}
public function store(StorePostRequest $request)
{
$post = $request->user()->posts()->create($request->validated());
return response()->json($post, 201);
}
}
Liaison de modèle de route (Route model binding)
Laravel peut transformer automatiquement un paramètre de route en instance de modèle. Nommez le paramètre d’après le modèle et spécifiez son type dans le contrôleur, et Laravel récupérera l’enregistrement ou retournera un 404.
Route::get('/posts/{post}', [PostController::class, 'show']);
public function show(Post $post)
{
return $post->load('author');
}
Par défaut, la clé primaire du modèle est utilisée. Ajoutez getRouteKeyName() pour effectuer la liaison sur un slug à la place, ce qui permet d’obtenir des URL propres comme /posts/hello-world sans code de recherche supplémentaire.
Eloquent : l’ORM pour lequel on reste
Eloquent est l’ORM active-record de Laravel. Un modèle correspond à une table, et les relations sont déclarées sous forme de méthodes.
class Post extends Model
{
protected $fillable = ['title', 'slug', 'body'];
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function scopePublished($query)
{
return $query->whereNotNull('published_at');
}
}
Les relations incluent hasMany, belongsTo, belongsToMany, hasManyThrough ainsi que des variantes polymorphes. La règle pratique la plus importante est d’utiliser le eager loading (chargement anticipé) pour les relations que vous savez être nécessaires :
$posts = Post::with('author')->published()->latest()->get();
Sans with('author'), l’accès à $post->author à l’intérieur d’une boucle déclenche une requête par ligne — le classique problème du N+1. Le eager loading transforme cela en deux requêtes, quel que soit le nombre de posts récupérés.
Migrations, factories et seeders
Les modifications de schéma sont des fichiers PHP versionnés, ce qui garantit que chaque environnement est construit à partir du même historique.
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->timestamps();
});
Exécutez-les avec php artisan migrate. Les factories décrivent comment simuler un modèle, et les seeders alimentent une base de données avec des données connues.
Post::factory()->count(50)->for(User::factory())->create();
Ensemble, les factories et les seeders rendent les tests réalistes sans avoir à écrire des fixtures à la main.
Blade : le templating sans surprises
Blade est compilé en PHP pur et ne vous gêne jamais. L’affichage est échappé par défaut, et les layouts ainsi que les composants permettent de garder un markup DRY.
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<p>By {{ $post->author->name }}</p>
{!! $post->body !!}
</article>
@endsection
{{ }} échappe le contenu, {!! !!} ne le fait pas. Utilisez la forme échappée partout, sauf si vous avez assaini le contenu vous-même, et privilégiez les composants comme <x-post-card :post="$post" /> pour les interfaces utilisateur réutilisables.
Validation et form requests
La validation est une fonctionnalité de premier plan. Pour tout ce qui dépasse un cas unique, déplacez vos règles dans une form request.
php artisan make:request StorePostRequest
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user() !== null;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
'published_at' => ['nullable', 'date'],
];
}
}
Utilisez le type-hinting de la requête dans le contrôleur et Laravel effectuera la validation avant l’exécution de votre code, en retournant automatiquement une 422 contenant les erreurs. Utilisez ensuite $request->validated() pour garantir que seuls les champs connus atteignent le modèle.
Middleware et cycle de vie de la requête
Le middleware filtre les requêtes avant qu’elles n’atteignent une route. Les versions récentes de Laravel les configurent dans bootstrap/app.php.
->withMiddleware(function (Middleware $middleware) {
$middleware->api(prepend: [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
]);
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
]);
})
Une requête passe d’abord par le middleware global, puis par le middleware de route tel que auth, et enfin par le contrôleur. Comprendre cet ordre permet de répondre à la plupart des questions du type « pourquoi cette valeur est-elle manquante ».
Le conteneur de services et les facades
Le conteneur de services résout les dépendances pour vous. Indiquez le type d’une classe dans un constructeur ou un contrôleur et Laravel l’instanciera, en injectant ses propres dépendances de manière récursive.
class PostController extends Controller
{
public function __construct(private PostRepository $posts) {}
}
Les facades comme Cache::get() et DB::table() offrent un raccourci d’apparence statique vers les services du conteneur. Elles sont pratiques dans les contrôleurs, mais l’injection par constructeur permet de garder les classes testables et les dépendances explicites. Privilégiez l’injection dans les services ; utilisez les facades là où elles améliorent réellement la lisibilité.
Artisan, files d’attente et événements
Artisan est l’interface en ligne de commande qui permet de générer et d’exécuter l’application.
php artisan make:model Post -mfc # model, migration, factory, controller
php artisan migrate
php artisan queue:work
php artisan schedule:run
Tout ce qui est lent — l’envoi d’un e-mail, l’appel à une API tierce, la génération d’un rapport — doit être placé dans un job mis en file d’attente.
class SendPostNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public function __construct(public Post $post) {}
public function handle(): void
{
// runs on a worker, not in the request
}
}
SendPostNotification::dispatch($post);
Les événements et les listeners suivent le même modèle, et les listeners peuvent implémenter ShouldQueue eux-mêmes, permettant ainsi à la requête de revenir immédiatement.
Authentification avec Sanctum
Pour les SPA et les API basées sur des jetons, Sanctum est le choix par défaut. Il émet des jetons d’accès personnels et prend également en charge les sessions basées sur les cookies pour les frontends propriétaires.
$token = $user->createToken('mobile')->plainTextToken;
Protégez vos routes avec l’alias du middleware :
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Passport reste l’option privilégiée lorsque vous avez besoin d’un serveur OAuth2 complet avec des clients, des scopes et des grants.
Tests avec Pest et PHPUnit
Laravel est livré avec une configuration de tests qui initialise le framework et peut réinitialiser la base de données pour chaque test grâce au trait RefreshDatabase.
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('it creates a post', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/posts', ['title' => 'Hello', 'body' => 'World'])
->assertCreated();
});
Pest ajoute une syntaxe basée sur des fonctions, plus lisible, par-dessus PHPUnit. Dans les deux cas, les tests HTTP sollicitent simultanément le routage, le middleware, la validation et la base de données, ce qui est précisément là où se situent la plupart des bugs Laravel.
Bonnes pratiques
- Gardez vos contrôleurs légers ; déplacez la logique métier dans des services et des actions.
- Utilisez des form requests pour la validation et ne persistez que
$request->validated(). - Utilisez le chargement anticipé (eager loading) des relations avec
with()pour éviter les requêtes N+1. - Versionnez votre schéma avec des migrations et générez vos données avec des factories.
- Privilégiez l’injection par constructeur aux facades dans les services que vous souhaitez tester.
- Mettez en file d’attente (queue) tout processus lent pour garantir des requêtes rapides.
- Rédigez des tests de fonctionnalités (feature tests) qui sollicitent la couche HTTP et une base de données réelle.
Erreurs courantes
- Appeler
Post::create($request->all())et exposer des colonnes à l’assignation de masse (mass assignment). - Effectuer le lazy loading de relations dans une boucle et saturer la base de données.
- Regrouper les requêtes, la validation et les règles métier dans une seule et unique méthode de contrôleur.
- Oublier
$fillableet se demander pourquoi certains attributs sont ignorés sans erreur. - Exécuter des tâches lourdes de manière synchrone au lieu de dispatcher un job.
- Modifier une ancienne migration après son exécution en production.
- Supposer que
{{ }}et{!! !!}sont interchangeables.
Et après ?
Laravel propose une approche productive et directive pour bâtir des applications web, et une grande partie de ses fondations repose sur les composants Symfony, ce guide en est donc le compagnon naturel. Si vous venez de l’univers JavaScript, comparez cette approche avec celle d’ Express. Pour concevoir l’API exposée par vos routes, consultez la section sur REST, puis celle sur OpenAPI pour la documentation.