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.