Laravel 12 (LTS)
🎯 Objectif : maîtriser Laravel 12 LTS au point de livrer un SaaS complet (auth, CRUD, queues, mail, broadcasting) en quelques jours.
À l'issue de cet axe, tu sauras :
- Construire une app Laravel avec routing, contrôleurs, Eloquent, validation, Blade
- Authentifier une SPA via Laravel Sanctum (cookies session)
- Mettre en file des jobs avec Redis et Horizon
- Tester avec Pest (DSL plus expressif que PHPUnit)
- Déployer via Forge ou Vapor
Pourquoi Laravel ?
Section intitulée « Pourquoi Laravel ? »| Pour | Contre |
|---|---|
| Productivité : artisan génère contrôleurs, modèles, migrations en 1 commande | Beaucoup de magie (façades, conteneur global) |
| Eloquent ORM : actif-record élégant | Difficile à utiliser sans framework |
| Tout intégré : auth, queues, mail, broadcasting, scheduling, cache | Convention Laravel à digérer |
| Écosystème mature : Forge, Vapor, Nova (admin), Horizon, Telescope | Verrouillage écosystème (à part Composer pur) |
| Pest ou PHPUnit, factories, model testing | Performances brutes inférieures à Symfony optimisé |
Verdict 2026 : Laravel reste le couteau suisse PHP pour livrer vite. Sauf cas spécifiques (gros enterprise, perf critique), c’est le bon défaut.
Installation rapide
Section intitulée « Installation rapide »# Avec composer globalcomposer global require laravel/installerlaravel new taskly-laravel --gitcd taskly-laravel
# Ou directementcomposer create-project laravel/laravel taskly-laravel
# Démarrerphp artisan serve# http://localhost:8000artisan est le CLI Laravel. Tu vas l’utiliser tous les jours.
Structure d’un projet Laravel 12
Section intitulée « Structure d’un projet Laravel 12 »taskly-laravel/├── app/│ ├── Http/│ │ ├── Controllers/ ← contrôleurs HTTP│ │ ├── Middleware/│ │ └── Requests/ ← Form Requests (validation)│ ├── Models/ ← modèles Eloquent│ ├── Jobs/ ← jobs en file│ ├── Mail/ ← classes de mail│ └── Providers/├── routes/│ ├── web.php ← routes "session web" (avec CSRF)│ ├── api.php ← routes API (stateless)│ └── console.php├── database/│ ├── migrations/│ ├── factories/│ └── seeders/├── resources/│ ├── views/ ← templates Blade│ └── ...├── tests/│ ├── Feature/│ └── Unit/├── config/└── .envuse App\Http\Controllers\TaskController;
Route::middleware('auth:sanctum')->group(function () { Route::get('/me', fn(Request $r) => $r->user());
Route::apiResource('tasks', TaskController::class); // → 5 routes : index, store, show, update, destroy});Route::apiResource génère automatiquement les 5 routes REST classiques.
Models avec Eloquent
Section intitulée « Models avec Eloquent »php artisan make:model Task -mfsr# -m : migration, -f : factory, -s : seeder, -r : controller resourcenamespace App\Models;
use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Task extends Model{ protected $fillable = ['title', 'description', 'done', 'due_at'];
protected $casts = [ 'done' => 'boolean', 'due_at' => 'datetime', ];
public function owner(): BelongsTo { return $this->belongsTo(User::class, 'owner_id'); }}Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->foreignId('owner_id')->constrained('users')->cascadeOnDelete(); $table->string('title'); $table->text('description')->nullable(); $table->boolean('done')->default(false); $table->timestamp('due_at')->nullable(); $table->timestamps();});php artisan migrateQuerysets
Section intitulée « Querysets »// Filtrer$tasks = Task::where('owner_id', $userId)->where('done', false)->get();
// Joindre (évite N+1)$tasks = Task::with('owner')->where('done', false)->get();
// Pagination$tasks = Task::where('owner_id', $userId)->latest()->paginate(20);// retourne LengthAwarePaginator avec data, total, current_page, last_page
// Update / delete par batchTask::where('owner_id', $userId)->update(['done' => true]);Controllers et Form Requests
Section intitulée « Controllers et Form Requests »php artisan make:controller TaskController --api --model=Taskphp artisan make:request StoreTaskRequestnamespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreTaskRequest extends FormRequest{ public function rules(): array { return [ 'title' => 'required|string|max:200', 'description' => 'nullable|string|max:2000', 'due_at' => 'nullable|date', ]; }}namespace App\Http\Controllers;
use App\Http\Requests\StoreTaskRequest;use App\Models\Task;use Illuminate\Http\Request;use Illuminate\Http\Response;
class TaskController extends Controller{ public function index(Request $request) { return $request->user()->tasks()->latest()->paginate(20); }
public function store(StoreTaskRequest $request) { $task = $request->user()->tasks()->create($request->validated()); return response()->json($task, Response::HTTP_CREATED); }
public function show(Request $request, Task $task) { $this->authorize('view', $task); return $task; }
public function update(StoreTaskRequest $request, Task $task) { $this->authorize('update', $task); $task->update($request->validated()); return $task; }
public function destroy(Request $request, Task $task) { $this->authorize('delete', $task); $task->delete(); return response()->noContent(); }}Magie Laravel : Task $task dans la signature → Laravel récupère automatiquement la tâche par son id dans l’URL. Si introuvable, 404 automatique. C’est le route model binding.
Authentification — Sanctum
Section intitulée « Authentification — Sanctum »Laravel Sanctum gère deux modes :
- API tokens (Bearer) pour des apps mobile / clients tiers.
- SPA cookies : sessions classiques avec CSRF — recommandé pour ton frontend Next.js / Vue.
php artisan install:api'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost:3000,localhost:5173')),Route::middleware('auth:sanctum')->group(function () { Route::get('/me', fn(Request $r) => $r->user()); Route::post('/logout', fn(Request $r) => tap($r, fn($r) => Auth::logout())->session()->invalidate());});
// Login simple (cookie session)Route::post('/login', function (Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]);
if (!Auth::attempt($credentials)) { return response()->json(['error' => 'Invalid credentials'], 401); }
$request->session()->regenerate(); return $request->user();});Avec Sanctum SPA, le frontend appelle d’abord /sanctum/csrf-cookie (récupère le CSRF token), puis /login avec ce token. Les requêtes suivantes sont automatiquement authentifiées par cookie.
Queues et jobs
Section intitulée « Queues et jobs »php artisan make:job SendWelcomeEmailnamespace App\Jobs;
use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;
class SendWelcomeEmail implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public int $userId) {}
public function handle(): void { // envoyer l'email }}
// Dispatch dans un controllerSendWelcomeEmail::dispatch($user->id);Configuration .env :
QUEUE_CONNECTION=redisDémarrer un worker :
php artisan queue:work# Ou en prod : Horizon (UI + monitoring)php artisan horizonAvantages : retries automatiques, exponential backoff, dead-letter queue, monitoring via Horizon.
Tests avec Pest
Section intitulée « Tests avec Pest »Pest est un DSL de test plus expressif que PHPUnit.
composer require pestphp/pest --devphp artisan pest:installuse App\Models\User;use App\Models\Task;
it('lists tasks of authenticated user', function () { $user = User::factory()->create(); Task::factory()->count(3)->for($user, 'owner')->create();
$response = $this->actingAs($user)->getJson('/api/tasks');
$response ->assertOk() ->assertJsonCount(3, 'data');});
it('returns 401 without auth', function () { $this->getJson('/api/tasks')->assertUnauthorized();});./vendor/bin/pestEloquent factories pour les tests
Section intitulée « Eloquent factories pour les tests »namespace Database\Factories;
use App\Models\Task;use Illuminate\Database\Eloquent\Factories\Factory;
class TaskFactory extends Factory{ protected $model = Task::class;
public function definition(): array { return [ 'title' => $this->faker->sentence(), 'description' => $this->faker->paragraph(), 'done' => false, ]; }}
// UsageTask::factory()->count(10)->create();Task::factory()->done()->for($user, 'owner')->create();Déploiement
Section intitulée « Déploiement »| Plateforme | Note |
|---|---|
| Forge (laravel.com/forge) | Provisionne ton VPS DigitalOcean/Hetzner, déploiement Git push, certificat Let’s Encrypt — sweet spot Laravel |
| Vapor | Serverless (AWS Lambda) — scale-to-zero, mais coûts à grande échelle |
| Render / Railway | PaaS génériques, OK pour Laravel |
| VPS + Frankenphp | Setup manuel, perfs maximales avec worker mode |
Auto-évaluation
Section intitulée « Auto-évaluation »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Laravel Documentation — laravel.com/docs
- Laracasts — laracasts.com (vidéos premium)
- Laravel News — laravel-news.com (newsletter de référence)
- Pest PHP — pestphp.com
- Awesome Laravel — github.com/chiraggude/awesome-laravel
Projet de l’axe — taskly-api en Laravel 12
Section intitulée « Projet de l’axe — taskly-api en Laravel 12 »Mêmes 10 endpoints que les versions Hono et FastAPI. Auth Sanctum (Bearer tokens), Form Requests (validation 422 auto), apiResource (5 routes en 1 ligne), Pest pour les tests, ~13 fichiers métier.