# Guide complet : Laravel 12 CRUD ToDoList from scratch ![Logo ToDoList](assets/todolist-logo.png) *Aperçu de l'interface finale :* ![Aperçu ToDoList](assets/todolist-screen.png) **Public cible :** Très débutant (style stagiaire) **Objectif :** Projet CRUD ToDoList avec 2 tables en relation (categories 1—N tasks) + pages Blade simples en HTML/CSS sans framework. --- ## Table des matières 1. [Prérequis et liens officiels](#1-prérequis-et-liens-officiels) 2. [Créer le dossier et le projet Laravel 12](#2-créer-le-dossier-et-le-projet-laravel-12) 3. [Ouvrir le projet dans VS Code](#3-ouvrir-le-projet-dans-vs-code) 4. [Configurer la base de données](#4-configurer-la-base-de-données) 5. [Schéma de la base (MLD/MCD)](#5-schéma-de-la-base-mldmcd) 6. [Migrations](#6-migrations) 7. [Models avec relations](#7-models-avec-relations) 8. [Controllers CRUD](#8-controllers-crud) 9. [Routes](#9-routes) 10. [Vues Blade – Layout](#10-vues-blade--layout) 11. [Vues Blade – Categories](#11-vues-blade--categories) 12. [Vues Blade – Tasks](#12-vues-blade--tasks) 13. [Résumé des commandes](#13-résumé-des-commandes) 14. [Checklist de debug](#14-checklist-de-debug) --- ## 1. Prérequis et liens officiels ### Requirements | Logiciel | Version minimale | Lien officiel | |----------|------------------|---------------| | **PHP** | 8.2+ | [php.net](https://php.net/) | | **Composer** | 2.x | [getcomposer.org](https://getcomposer.org/) | | **MySQL** | 5.7+ ou 8.x | Via XAMPP | | **VS Code** | Dernière stable | [code.visualstudio.com](https://code.visualstudio.com/) | | **XAMPP** | 8.x (PHP + MySQL) | [apachefriends.org](https://www.apachefriends.org/) | ### Extensions PHP requises - **Vérifier :** `php -v` et `composer -v` ![php -v et composer -v](assets/c-est-v.png) ### Liens utiles > **Piège fréquent :** Si `php` ou `composer` n’est pas reconnu dans le terminal, ajoutez-les au PATH système (répertoire `php` et `composer` dans XAMPP). --- **NEXT :** Passer à l’étape 2 – Créer le dossier et le projet Laravel 12. --- ## 2. Créer le dossier et le projet Laravel 12 ### Étapes 1. Ouvrir **CMD** dans un dossier (ex. `C:\Users\Admin\Desktop\laravel`). 2. Démarrer **XAMPP** (Apache + MySQL) et créer la base `todolist_db` dans phpMyAdmin. 3. Exécuter : ```bash composer create-project laravel/laravel todolist cd todolist php artisan serve ``` **Explication :** Le point `.` crée le projet dans le dossier courant. `^12.0` impose Laravel 12. > **Piège fréquent :** Ne pas oublier le point final. Sinon Composer crée un sous-dossier `laravel`. 4. Base `todolist_db` dans phpMyAdmin. - Aller sur `http://localhost/phpmyadmin` - Créer une base nommée `todolist_db` (interclassement : `utf8mb4_unicode_ci`). 6. Lancer le serveur Laravel : ```bash php artisan serve ``` L’application sera accessible sur `http://localhost:8000`. > **Piège fréquent :** Vérifier que le port 8000 n’est pas déjà utilisé. Sinon : `php artisan serve --port=8080`. --- **NEXT :** Ouvrir le projet dans VS Code avant de modifier `.env`. --- ## 3. Ouvrir le projet dans VS Code Dans le dossier du projet (ex. `todolist-app`), exécuter : ```bash code . ``` **Explication :** Ouvre VS Code avec le projet comme workspace. Pensez à installer l’extension [Laravel](https://marketplace.visualstudio.com/items?itemName=laravel.vscode-laravel) pour VS Code. > **Piège fréquent :** Si `code` n’est pas reconnu, ajoutez VS Code au PATH ou ouvrez le dossier manuellement (Fichier → Ouvrir un dossier). --- **NEXT :** Configurer la base de données dans `.env`. --- ## 4. Configurer la base (.env) Modifier `.env` : `DB_DATABASE=todolist_db`, `DB_USERNAME=root`, `DB_PASSWORD=` Puis : ``` php artisan config:clear ``` --- **NEXT :** Créer les migrations et le schéma de base. --- ## 5. Schéma de la base (MLD/MCD) ### MCD (Modèle Conceptuel de Données) ``` ┌─────────────────────┐ ┌─────────────────────┐ │ CATEGORY │ │ TASK │ ├─────────────────────┤ ├─────────────────────┤ │ id (PK) │ 1 N │ id (PK) │ │ name │─────────│ category_id (FK) │ │ created_at │ │ title │ │ updated_at │ │ done (boolean) │ └─────────────────────┘ │ due_date (nullable) │ │ created_at │ │ updated_at │ └─────────────────────┘ ``` **Relation :** 1 Category → N Tasks (clé étrangère `category_id` avec `onDelete('cascade')`). --- **NEXT :** Créer les migrations. --- ## 6. Migrations **C'est quoi une migration ?** Un fichier PHP qui décrit la structure d'une table (colonnes, types). Laravel l'exécute pour créer les tables dans MySQL. **Comment ça marche dans le projet ?** Les migrations sont dans `database/migrations/`. Chaque fichier = 1 table. La commande `php artisan migrate` exécute tout et crée les tables. --- ### Créer les migrations ```bash php artisan make:migration create_categories_table php artisan make:migration create_tasks_table ``` ### Migration `categories` Ouvrir `database/migrations/xxxx_create_categories_table.php` et remplacer la méthode `up()` par : ```php public function up(): void { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); } ``` ### Migration `tasks` Ouvrir `database/migrations/xxxx_create_tasks_table.php` et remplacer la méthode `up()` par : ```php public function up(): void { Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->foreignId('category_id')->constrained('categories')->onDelete('cascade'); $table->string('title'); $table->boolean('done')->default(false); $table->date('due_date')->nullable(); $table->timestamps(); }); } ``` **Explication :** `constrained()->onDelete('cascade')` supprime les tâches d’une liste quand celle-ci est supprimée. > **Piège fréquent :** Créer la migration `tasks` APRÈS `categories`, sinon erreur de clé étrangère. ### Exécuter les migrations ```bash php artisan migrate ``` Vérifier dans phpMyAdmin : tables `categories` et `tasks`. --- **NEXT :** Créer les models avec les relations. --- ## 7. Models avec relations ### Créer les models ```bash php artisan make:model Category php artisan make:model Task ``` ### Model `Category` Fichier `app/Models/Category.php` : ```php hasMany(Task::class); } } ``` ### Model `Task` Fichier `app/Models/Task.php` : ```php 'boolean', 'due_date' => 'date', ]; public function category(): BelongsTo { return $this->belongsTo(Category::class); } } ``` **Explication :** `$fillable` autorise l’assignation de masse. `$casts` convertit `done` en booléen et `due_date` en date. --- **NEXT :** Créer les controllers CRUD. --- ## 8. Controllers CRUD ### Créer les controllers (ressources) ```bash php artisan make:controller CategoryController --resource php artisan make:controller TaskController --resource ``` **Pourquoi `--resource` ?** Génère automatiquement les 7 méthodes CRUD : index, create, store, show, edit, update, destroy. **Commande alternative (sans --resource) :** ```bash # php artisan make:controller CategoryController → controller vide, à remplir à la main ``` ### `CategoryController` Fichier `app/Http/Controllers/CategoryController.php` : ```php orderBy('created_at', 'desc')->get(); return view('categories.index', compact('categories')); } public function create() { return view('categories.create'); } public function store(Request $request) { $request->validate([ 'name' => 'required|string|max:255', ]); Category::create($request->only('name')); return redirect()->route('categories.index')->with('success', 'Catégorie créée !'); } public function show(Category $category) { $category->load('tasks'); return view('categories.show', compact('category')); } public function edit(Category $category) { return view('categories.edit', compact('category')); } public function update(Request $request, Category $category) { $request->validate([ 'name' => 'required|string|max:255', ]); $category->update($request->only('name')); return redirect()->route('categories.index')->with('success', 'Catégorie modifiée !'); } public function destroy(Category $category) { $category->delete(); return redirect()->route('categories.index')->with('success', 'Catégorie supprimée !'); } } ``` ### `TaskController` Fichier `app/Http/Controllers/TaskController.php` : ```php orderBy('created_at', 'desc') ->paginate(10); return view('tasks.index', compact('tasks')); } public function create(Request $request) { $categories = Category::orderBy('name')->get(); $categoryId = $request->query('category'); return view('tasks.create', compact('categories', 'categoryId')); } public function store(Request $request) { $request->validate([ 'category_id' => 'required|exists:categories,id', 'title' => 'required|string|max:255', 'done' => 'boolean', 'due_date' => 'nullable|date', ]); $data = $request->only('category_id', 'title', 'due_date'); $data['done'] = $request->boolean('done'); Task::create($data); return redirect()->route('tasks.index')->with('success', 'Tâche créée !'); } public function show(Task $task) { $task->load('category'); return view('tasks.show', compact('task')); } public function edit(Task $task) { $categories = Category::orderBy('name')->get(); return view('tasks.edit', compact('task', 'categories')); } public function update(Request $request, Task $task) { $request->validate([ 'category_id' => 'required|exists:categories,id', 'title' => 'required|string|max:255', 'done' => 'boolean', 'due_date' => 'nullable|date', ]); $data = $request->only('category_id', 'title', 'due_date'); $data['done'] = $request->boolean('done'); $task->update($data); return redirect()->route('tasks.index')->with('success', 'Tâche modifiée !'); } public function destroy(Task $task) { $task->delete(); return redirect()->route('tasks.index')->with('success', 'Tâche supprimée !'); } } ``` > **Piège fréquent :** Pour les checkboxes, utiliser `$request->boolean('done')` car une checkbox non cochée n’est pas envoyée dans la requête. --- **NEXT :** Déclarer les routes. --- ## 9. Routes Ouvrir `routes/web.php` et remplacer tout le contenu par : ```php route('categories.index'); }); Route::resource('categories', CategoryController::class); Route::resource('tasks', TaskController::class); ``` **Vérifier les routes :** ```bash php artisan route:list ``` --- **NEXT :** Créer les vues Blade. --- ## 10. Vues Blade – Layout ### Récapitulatif du projet (Model, Migration, Controller, Vue) | Élément | Où ? | C'est quoi ? | |--------|------|--------------| | **Model** | `app/Models/` | Représente une table. `$fillable` = colonnes modifiables. Relations : `hasMany`, `belongsTo`. | | **Migration** | `database/migrations/` | Fichier PHP qui crée/modifie les tables. `Schema::create()` définit les colonnes. | | **Controller** | `app/Http/Controllers/` | Reçoit la requête, lit/écrit via le Model, retourne une Vue. Méthodes : index, create, store, show, edit, update, destroy. | | **Vue Blade** | `resources/views/` | Template HTML avec `{{ }}` et `@if`. Hérite du layout via `@extends`. | **Flux :** Route → Controller → Model (DB) → Vue → HTML **Contenu des Migrations (résumé) :** `categories` = id, name, timestamps. `tasks` = id, category_id (FK), title, done, due_date, timestamps. **Contenu des Models (résumé) :** Category = $fillable ['name'], tasks() hasMany. Task = $fillable ['category_id','title','done','due_date'], $casts, category() belongsTo. **Contenu des Controllers (résumé) :** index→get+view, create→view, store→validate+create+redirect, show→load+view, edit→view, update→validate+update+redirect, destroy→delete+redirect. --- ### Créer le dossier et le layout ```bash mkdir resources\views\categories mkdir resources\views\tasks ``` ### Layout principal Fichier `resources/views/layouts/app.blade.php` : ```html @yield('title', 'ToDoList') - Laravel 12

📋 ToDoList Laravel 12

@if(session('success'))
{{ session('success') }}
@endif @if($errors->any())
@endif @yield('content')
``` --- **NEXT :** Créer les vues Categories. --- ## 11. Vues Blade – Categories ### `categories/index.blade.php` ```html @extends('layouts.app') @section('title', 'Catégories') @section('content')

Mes catégories

+ Nouvelle catégorie

@forelse($categories as $category)
{{ $category->name }} ({{ $category->tasks_count }} tâches)
Modifier
@csrf @method('DELETE')
@empty

Aucune catégorie. Créez-en une !

@endforelse
@endsection ``` ### `categories/create.blade.php` ```html @extends('layouts.app') @section('title', 'Nouvelle catégorie') @section('content')

Nouvelle catégorie

@csrf
Annuler
@endsection ``` ### `categories/edit.blade.php` ```html @extends('layouts.app') @section('title', 'Modifier la catégorie') @section('content')

Modifier la catégorie

@csrf @method('PUT')
Annuler
@endsection ``` ### `categories/show.blade.php` ```html @extends('layouts.app') @section('title', $category->name) @section('content')

{{ $category->name }}

+ Tâche Modifier
@csrf @method('DELETE')

Tâches

@forelse($category->tasks as $task)
done ? 'checked' : '' }}> {{ $task->title }} @if($task->done)✓ Fait@endif @if($task->due_date)({{ $task->due_date->format('d/m/Y') }})@endif
Modifier
@empty

Aucune tâche.

@endforelse

← Retour aux catégories

@endsection ``` --- **NEXT :** Créer les vues Tasks. --- ## 12. Vues Blade – Tasks ### `tasks/index.blade.php` ```html @extends('layouts.app') @section('title', 'Tâches') @section('content')

Toutes les tâches

+ Nouvelle tâche

@forelse($tasks as $task)
done ? 'checked' : '' }}> {{ $task->title }} — {{ $task->category->name }} @if($task->done)@endif @if($task->due_date)({{ $task->due_date->format('d/m/Y') }})@endif
Modifier
@csrf @method('DELETE')
@empty

Aucune tâche.

@endforelse
@endsection ``` ### `tasks/create.blade.php` ```html @extends('layouts.app') @section('title', 'Nouvelle tâche') @section('content')

Nouvelle tâche

@csrf
Annuler
@endsection ``` ### `tasks/edit.blade.php` ```html @extends('layouts.app') @section('title', 'Modifier la tâche') @section('content')

Modifier la tâche

@csrf @method('PUT')
Annuler
@endsection ``` ### `tasks/show.blade.php` ```html @extends('layouts.app') @section('title', $task->title) @section('content')

{{ $task->title }}

Liste : {{ $task->category->name }}

Statut : {{ $task->done ? '✓ Fait' : 'Non fait' }}

@if($task->due_date)

Échéance : {{ $task->due_date->format('d/m/Y') }}

@endif
Modifier
@csrf @method('DELETE')
← Retour aux tâches
@endsection ``` --- **NEXT :** Corriger le chargement des relations dans l’index des Categorys. --- ### Fix : charger les tâches dans l’index des listes Dans `CategoryController::index()`, charger le comptage des tâches : ```php $categories = Category::withCount('tasks')->orderBy('created_at', 'desc')->get(); ``` *Déjà appliqué dans le guide ci-dessus (withCount + tasks_count).* --- ### Styliser la pagination Laravel 12 utilise Tailwind par défaut pour la pagination. Pour du CSS simple, publier les vues de pagination : ```bash php artisan vendor:publish --tag=laravel-pagination ``` Ensuite éditer `resources/views/vendor/pagination/default.blade.php` et adapter les classes aux styles du layout (ou garder les classes Bootstrap si vous préférez). Pour rester simple, on peut utiliser `simplePaginate(10)` : Dans `TaskController::index()` : ```php $tasks = Task::with('category') ->orderBy('created_at', 'desc') ->simplePaginate(10); ``` Cela affichera des liens « Précédent » / « Suivant » sans design complexe. --- ## 13. Résumé des commandes | # | Commande | |---|----------| | 1 | `composer create-project laravel/laravel todolist` puis `cd todolist` puis `php artisan serve` | | 2 | `code .` | | 3 | `php artisan config:clear` | | 4 | `php artisan make:migration create_categories_table` | | 5 | `php artisan make:migration create_tasks_table` | | 6 | `php artisan migrate` | | 7 | `php artisan make:model Category` | | 8 | `php artisan make:model Task` | | 9 | `php artisan make:controller CategoryController --resource` | | 10 | `php artisan make:controller TaskController --resource` | | 11 | `mkdir resources\views\categories` | | 12 | `mkdir resources\views\tasks` | | 13 | `php artisan serve` | | 14 | `php artisan route:list` | --- ## 14. Checklist de debug | Problème | Solution | |----------|----------| | **Erreur DB / connexion refusée** | Vérifier que MySQL (XAMPP) est démarré. Vérifier `.env` (DB_DATABASE, DB_USERNAME, DB_PASSWORD). | | **Erreur 419 CSRF token mismatch** | Inclure `@csrf` dans tous les formulaires. Vérifier la meta `csrf-token` dans le layout. Vider le cache : `php artisan config:clear` et `php artisan cache:clear`. | | **Route not found (404)** | Exécuter `php artisan route:list` pour vérifier les routes. Vérifier que `Route::resource` est bien déclaré. | | **Erreur migration / foreign key** | Lancer les migrations dans l’ordre : d’abord `categories`, puis `tasks`. Si besoin : `php artisan migrate:fresh` (attention, efface toutes les données). | | **Variable undefined dans une vue** | Vérifier que le controller passe bien la variable avec `compact()` ou un tableau. | | **Checkbox non cochée = erreur** | Utiliser `$request->boolean('done')` au lieu de `$request->input('done')`. | | **Pagination ne s’affiche pas** | Vérifier que le modèle utilise `->paginate(10)` ou `->simplePaginate(10)` et que la vue affiche `{{ $tasks->links() }}`. | --- **Fin du guide.** Bon courage pour votre projet ToDoList Laravel 12.