Installation & Structure

# Créer un nouveau projet Laravel 11
composer create-project laravel/laravel mon-blog

cd mon-blog

# Démarrer le serveur de développement
php artisan serve
# → http://127.0.0.1:8000

# Avec SQLite (le plus simple pour commencer)
touch database/database.sqlite
# dans .env : DB_CONNECTION=sqlite

# Lancer les migrations
php artisan migrate

Structure des dossiers

mon-blog/
├── app/
│   ├── Http/
│   │   ├── Controllers/   ← Tes controllers
│   │   ├── Middleware/    ← Middleware custom
│   │   └── Requests/      ← Form Requests (validation)
│   ├── Models/            ← Modèles Eloquent
│   └── Providers/         ← Service Providers
├── bootstrap/
│   └── app.php            ← Bootstrap de l'app (Laravel 11)
├── config/                ← Fichiers de config
├── database/
│   ├── migrations/        ← Migrations DB
│   ├── factories/         ← Factories pour les tests/seeds
│   └── seeders/           ← Seeders
├── public/
│   └── index.php          ← Point d'entrée HTTP
├── resources/
│   └── views/             ← Templates Blade
├── routes/
│   ├── web.php            ← Routes web (sessions, CSRF)
│   └── api.php            ← Routes API (stateless)
├── storage/               ← Logs, cache, uploads
├── tests/                 ← Tests PHPUnit
├── .env                   ← Variables d'environnement
└── composer.json

Cycle de vie d'une requête

public/index.php Bootstrap App Kernel (middleware globaux) Router Middleware de route Controller Response
// public/index.php — point d'entrée unique
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

Routing

// routes/web.php — Routes web (avec session, CSRF)
use App\Http\Controllers\ArticleController;
use Illuminate\Support\Facades\Route;

// Routes basiques
Route::get('/', fn() => view('welcome'));
Route::get('/about', fn() => 'À propos');

// Paramètres de route
Route::get('/articles/{id}', function(int $id) {
    return "Article #{$id}";
});

// Paramètres optionnels
Route::get('/users/{name?}', function(?string $name = 'Invité') {
    return "Bonjour {$name}";
});

// Contraintes regex
Route::get('/articles/{id}', [ArticleController::class, 'show'])
    ->where('id', '[0-9]+');

// Named routes
Route::get('/articles/{article}', [ArticleController::class, 'show'])
    ->name('articles.show');

// Générer une URL depuis un nom
$url = route('articles.show', ['article' => 1]);
// → http://localhost/articles/1

// Groupes
Route::prefix('admin')->middleware('auth')->group(function() {
    Route::get('/dashboard', [AdminController::class, 'index'])->name('admin.dashboard');
    Route::resource('users', AdminUserController::class);
});

// Route::resource — 7 routes CRUD
Route::resource('articles', ArticleController::class);
// GET    /articles          → index
// GET    /articles/create   → create
// POST   /articles          → store
// GET    /articles/{id}     → show
// GET    /articles/{id}/edit → edit
// PUT    /articles/{id}     → update
// DELETE /articles/{id}     → destroy

// Route::apiResource — 5 routes (sans create/edit)
Route::apiResource('articles', ArticleController::class);
# Voir toutes les routes
php artisan route:list

# Filtrer par préfixe
php artisan route:list --path=api

# Voir seulement les noms de routes
php artisan route:list --columns=name,uri,method
Route Model Binding : Si le paramètre correspond au nom du modèle, Laravel injecte l'objet automatiquement : Route::get('/articles/{article}', [ArticleController::class, 'show'])public function show(Article $article). Si non trouvé → 404 automatique.

Controllers

# Créer un controller
php artisan make:controller ArticleController

# Resource controller (7 méthodes)
php artisan make:controller ArticleController --resource

# API controller (5 méthodes, sans create/edit)
php artisan make:controller Api/ArticleController --api

# Single action controller (__invoke)
php artisan make:controller PublishArticleController --invokable
// app/Http/Controllers/ArticleController.php
namespace App\Http\Controllers;

use App\Http\Requests\StoreArticleRequest;
use App\Http\Requests\UpdateArticleRequest;
use App\Models\Article;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class ArticleController extends Controller {

    // GET /articles
    public function index(Request $request): JsonResponse {
        $articles = Article::latest()->paginate(15);
        return response()->json(['data' => $articles]);
    }

    // POST /articles
    public function store(StoreArticleRequest $request): JsonResponse {
        $article = Article::create($request->validated());
        return response()->json(['data' => $article], 201);
    }

    // GET /articles/{article}
    public function show(Article $article): JsonResponse {
        return response()->json(['data' => $article]);
    }

    // PUT /articles/{article}
    public function update(UpdateArticleRequest $request, Article $article): JsonResponse {
        $article->update($request->validated());
        return response()->json(['data' => $article]);
    }

    // DELETE /articles/{article}
    public function destroy(Article $article): JsonResponse {
        $article->delete();
        return response()->json(null, 204);
    }
}

// Single Action Controller
class PublishArticleController extends Controller {
    public function __invoke(Article $article): JsonResponse {
        $article->update(['status' => 'published', 'published_at' => now()]);
        return response()->json(['data' => $article]);
    }
}
// Route : Route::post('/articles/{article}/publish', PublishArticleController::class)

Request

use Illuminate\Http\Request;

public function store(Request $request): JsonResponse {
    // Récupérer des valeurs
    $title   = $request->input('title');
    $title   = $request->title;          // propriété magique
    $all     = $request->all();          // tout
    $only    = $request->only(['title', 'body']); // whitelist
    $except  = $request->except(['_token']);       // blacklist

    // Valeurs par défaut
    $page = $request->input('page', 1);

    // Vérifications
    $request->has('title');        // clé présente
    $request->filled('title');     // présente ET non vide
    $request->missing('title');    // absente
    $request->isMethod('post');    // méthode HTTP
    $request->is('api/*');         // pattern URL
    $request->ajax();              // requête AJAX (XMLHttpRequest)
    $request->expectsJson();       // Accept: application/json

    // Utilisateur authentifié
    $user = $request->user();      // null si non auth
    $id   = $request->user()?->id;

    // Headers
    $accept = $request->header('Accept');
    $bearer = $request->bearerToken(); // Authorization: Bearer xxx

    // Fichiers
    if ($request->hasFile('avatar')) {
        $file = $request->file('avatar');
        $path = $file->store('avatars', 'public');
    }

    // Validation directe (simple)
    $validated = $request->validate([
        'title' => 'required|string|max:255',
        'body'  => 'required|string',
        'tags'  => 'array',
        'tags.*'=> 'string|max:50',
    ], [
        'title.required' => 'Le titre est obligatoire.',
        'body.required'  => 'Le contenu est obligatoire.',
    ]);

    Article::create($validated);
    return response()->json(['message' => 'Créé'], 201);
}

Response

// JSON
return response()->json(['data' => $articles]);
return response()->json(['data' => $article], 201);  // 201 Created
return response()->json(null, 204);                   // 204 No Content
return response()->json(['error' => 'Not found'], 404);

// Avec headers
return response()->json($data)
    ->header('X-Total-Count', 100)
    ->header('Cache-Control', 'no-store');

// Vue Blade
return view('articles.index', ['articles' => $articles]);
return view('articles.show', compact('article'));

// Redirections
return redirect('/');
return redirect()->route('articles.show', $article);
return redirect()->back();
return redirect()->back()->with('success', 'Article créé !');

// Abort (lève une exception → HTTP response)
abort(404);
abort(403, 'Accès refusé');
abort_if(!$user->isAdmin(), 403);
abort_unless($user->owns($article), 403);

// Téléchargement
return response()->download(storage_path('files/doc.pdf'), 'documentation.pdf');

// Streaming
return response()->streamDownload(function() {
    echo 'contenu du fichier...';
}, 'export.csv');

Artisan CLI

# ── Generators ─────────────────────────────
php artisan make:model Article -mfsc     # Model + Migration + Factory + Seeder + Controller
php artisan make:controller ArticleController --resource
php artisan make:middleware CheckRole
php artisan make:request StoreArticleRequest
php artisan make:resource ArticleResource
php artisan make:event ArticlePublished
php artisan make:listener SendPublishedNotification --event=ArticlePublished
php artisan make:job SendEmailDigest
php artisan make:notification ArticleCommented
php artisan make:policy ArticlePolicy --model=Article
php artisan make:seeder ArticleSeeder
php artisan make:factory ArticleFactory

# ── Database ────────────────────────────────
php artisan migrate                      # Lancer les migrations
php artisan migrate:rollback             # Annuler la dernière migration
php artisan migrate:fresh --seed         # Tout recréer + seeder
php artisan db:seed                      # Lancer les seeders
php artisan db:seed --class=ArticleSeeder

# ── Cache & Optimisation ────────────────────
php artisan optimize                     # Cache routes + config + vues
php artisan optimize:clear               # Vider tous les caches
php artisan config:cache                 # Cache la config
php artisan route:cache                  # Cache les routes
php artisan view:cache                   # Compile les vues Blade

# ── Dev ─────────────────────────────────────
php artisan tinker                       # REPL interactif
php artisan route:list                   # Lister les routes
php artisan queue:work                   # Démarrer le worker de queue
php artisan schedule:work                # Démarrer le scheduler (dev)

# ── Tests ───────────────────────────────────
php artisan test                         # Lancer tous les tests
php artisan test --filter=ArticleTest    # Filtrer
php artisan test --coverage              # Avec couverture de code

Créer une commande Artisan custom

php artisan make:command SendDailyDigest
// app/Console/Commands/SendDailyDigest.php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class SendDailyDigest extends Command {
    protected $signature   = 'app:send-daily-digest {--dry-run : Simuler sans envoyer}';
    protected $description = 'Envoie le digest quotidien aux utilisateurs';

    public function handle(): int {
        if ($this->option('dry-run')) {
            $this->info('Mode simulation — aucun email envoyé');
        }

        $count = 0;
        // $users = User::whereSubscribed()->get();
        // foreach ($users as $user) { Mail::to($user)->send(new DigestMail()); $count++; }

        $this->info("✓ Digest envoyé à {$count} utilisateurs");
        return Command::SUCCESS;
    }
}

// Enregistrement dans bootstrap/app.php (Laravel 11)
// Automatique — les commandes sont auto-découvertes

Configuration

# .env — variables d'environnement
APP_NAME="Mon Blog"
APP_ENV=local          # local, production, testing
APP_KEY=base64:xxx     # généré par php artisan key:generate
APP_DEBUG=true         # false en production !
APP_URL=http://localhost

DB_CONNECTION=sqlite
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=mon_blog
# DB_USERNAME=root
# DB_PASSWORD=

CACHE_DRIVER=file
QUEUE_CONNECTION=database
MAIL_MAILER=smtp
// Accéder aux valeurs de config
$name = config('app.name');          // lit config/app.php → 'name'
$debug = config('app.debug', false); // avec valeur par défaut
$dbHost = config('database.connections.mysql.host');

// Lire .env directement (moins recommandé)
$key = env('APP_KEY');

// Modifier à la volée (runtime, non persisté)
config(['app.debug' => true]);

// config/app.php — exemple de fichier de config
return [
    'name'  => env('APP_NAME', 'Laravel'),
    'env'   => env('APP_ENV', 'production'),
    'debug' => (bool) env('APP_DEBUG', false),
    'url'   => env('APP_URL', 'http://localhost'),
    // ...
];
Ne jamais lire env() dans les classes — utiliser config() à la place. Les valeurs .env ne sont pas disponibles après php artisan config:cache.
← Module 01 ✏️ Exercices ▶ Mini-projet 🧠 QCM Module 03 →