MVC en programmation : c'est quoi ? Exemple PHP + MySQL
C'est quoi le MVC en programmation ? Un CRUD PHP + MySQL rangé en Model, View, Controller et Router, sans framework, avec le code complet.
Aussi en : العربية

Le MVC (Model, View, Controller) est une façon d'organiser une application en trois rôles : le Model parle à la base de données, la View affiche le HTML, et le Controller reçoit la demande et coordonne les deux. Dans le cours E10, on applique cette idée à un vrai CRUD PHP + MySQL, sans framework, et on ajoute un Router qui fait entrer toutes les demandes par une seule porte. C'est l'organisation que tu retrouveras ensuite dans Laravel.
Pourquoi un seul fichier finit par tout casser ?
Au départ, tout tient dans index.php : la connexion, la requête SQL, le traitement du formulaire et le tableau HTML. Voici, en version courte, le fichier montré au début du cours :
<?php require "db.php";
$users = $pdo->query("SELECT * FROM users")->fetchAll();
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$pdo->prepare("INSERT INTO users ...")->execute([...]);
}
?>
<h1>Les utilisateurs</h1>
<table>
<?php foreach ($users as $u): ?>
<tr><td><?= $u["nom"] ?></td></tr>
<?php endforeach; ?>
</table>SQL, logique et HTML sont mélangés. Tant que le projet est petit, ça passe. Quand il grandit, on ne sait plus où modifier, et une petite retouche peut casser autre chose.
C'est quoi le MVC, avec une image simple ?
Le cours utilise l'exemple du restaurant :
- le Controller est le serveur : il prend la commande et sait qui doit travailler ;
- le Model est la cuisine : il parle à MySQL et prépare les données ;
- la View est l'assiette : elle présente les données en HTML, et rien d'autre.
Dans les dossiers, cela donne :
app/
Models/User.php
Views/users/index.php
Controllers/UserController.php
config/db.php
public/index.php ← la seule porte d'entrée du siteLe Model : le seul qui parle à la base
Dans app/Models/User.php, chaque requête SQL du CRUD devient une méthode. La classe reçoit le $pdo de db.php :
<?php
class User {
public function __construct(private PDO $pdo) {}
public function all(): array {
return $this->pdo->query("SELECT * FROM users")->fetchAll();
}
public function find(int $id): array|false {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
public function create(string $nom, string $email): void {
$stmt = $this->pdo->prepare("INSERT INTO users (nom, email) VALUES (?, ?)");
$stmt->execute([$nom, $email]);
}
// update() et delete() : même principe
}La règle : le Model ne connaît ni le HTML ni $_POST. Il ne manipule que des données. Deux bénéfices montrés dans le cours : si tu as besoin de la même requête ailleurs, tu appelles la méthode au lieu de réécrire le SQL ; et si un jour tu changes de base, seul le Model bouge.
La View : du HTML propre, sans SQL
app/Views/users/index.php ne contient plus que le tableau. Elle reçoit $users déjà prêt :
<h1>Les utilisateurs</h1>
<a href="/users/create">Ajouter</a>
<table>
<?php foreach ($users as $u): ?>
<tr>
<td><?= htmlspecialchars($u["nom"]) ?></td>
<td><?= htmlspecialchars($u["email"]) ?></td>
<td><a href="/users/edit?id=<?= $u["id"] ?>">Modifier</a></td>
</tr>
<?php endforeach; ?>
</table>Aucun SELECT, aucun $pdo : la View n'interroge pas la base. En revanche, htmlspecialchars est toujours dans la View, parce que c'est elle qui affiche, donc c'est elle qui protège du XSS. La syntaxe <?= avec endforeach garde un HTML lisible, même pour quelqu'un qui ne connaît pas PHP. Et pour changer le design, tu ne touches qu'à la View.
Le Controller : il coordonne, sans SQL ni HTML
app/Controllers/UserController.php contient une méthode par page. La méthode index tient en une ligne utile, plus l'appel à render :
<?php
class UserController {
public function __construct(private User $user) {}
public function index(): void {
$users = $this->user->all();
$this->render("users/index", ["users" => $users]);
}
private function render(string $view, array $data): void {
extract($data); // ["users" => …] devient $users
require __DIR__ . "/../Views/$view.php";
}
}render est une petite fonction : extract transforme le tableau en variables, puis require charge la View.
Où mettre la validation d'un formulaire ?
Dans le Controller, pas dans la View. La méthode store lit $_POST, vérifie l'email, puis appelle le Model :
public function store(): void {
$email = trim($_POST["email"] ?? "");
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->render("users/create", ["erreur" => "Email invalide"]);
return;
}
$this->user->create($_POST["nom"], $email);
header("Location: /users");
exit;
}Si l'email est invalide, on renvoie le formulaire avec un message. Sinon, header("Location: …") et exit, comme dans le CRUD. Le Controller n'écrit ni SQL ni HTML.
Le Router : comment savoir quel Controller appeler ?
Toutes les demandes entrent par public/index.php, qu'on appelle le front controller. Le fichier .htaccess y envoie n'importe quelle URL, sans exposer les fichiers PHP :
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]Dans index.php, un tableau associe chaque couple « méthode + chemin » à un Controller et une méthode :
<?php
require __DIR__ . "/../config/db.php";
require __DIR__ . "/../app/Models/User.php";
require __DIR__ . "/../app/Controllers/UserController.php";
$routes = [
"GET /users" => [UserController::class, "index"],
"GET /users/create" => [UserController::class, "create"],
"POST /users" => [UserController::class, "store"],
"GET /users/edit" => [UserController::class, "edit"],
];
$method = $_SERVER["REQUEST_METHOD"];
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$route = $routes["$method $path"] ?? null;
if (!$route) { http_response_code(404); exit("Page introuvable"); }
[$class, $action] = $route;
(new $class(new User($pdo)))->$action();Si la route n'existe pas, le visiteur reçoit une vraie 404, pas une page blanche pleine d'erreurs.
Le parcours complet d'une requête GET /users
- Le navigateur demande
/users. public/index.phpla reçoit ; le Router l'envoie àUserController::index().index()demande les utilisateurs au ModelUser.- Le Model fait un
SELECTdans MySQL et renvoie le résultat. - Le Controller transmet
$usersà la View. - La View construit le HTML, qui repart vers le navigateur.
Chacun fait son travail sans se mêler de celui des autres.
Et le login du cours précédent ?
Il devient une méthode privée du Controller, appelée au début de chaque page protégée :
private function requireLogin(): void {
if (!isset($_SESSION["user_id"])) { // cours E09
header("Location: /login");
exit;
}
}
public function index(): void {
$this->requireLogin();
$users = $this->user->all();
$this->render("users/index", ["users" => $users]);
}Quel rapport avec Laravel ?
Le récap du cours met les deux organisations côte à côte :
| Rôle | Dans ce cours | Dans Laravel |
|---|---|---|
| Model | app/Models/User.php | app/Models/User.php |
| View | app/Views/users/ | resources/views/users/ |
| Controller | app/Controllers/ | app/Http/Controllers/ |
| Router | public/index.php | routes/web.php |
Si tu as compris cette version faite à la main, Laravel te paraîtra beaucoup plus simple : tu sauras ce que le framework fait pour toi.
Questions fréquentes
Où mettre le SQL dans une application MVC ?
Dans le Model, et seulement là. Dans le cours, chaque requête du CRUD devient une méthode de User (all, find, create, update, delete). Le Controller appelle ces méthodes, la View ne voit jamais $pdo.
Faut-il un framework pour faire du MVC en PHP ?
Non. Le cours construit un MVC complet en PHP pur : trois dossiers, une fonction render et un Router de quelques lignes dans public/index.php. Un framework comme Laravel automatise ces mêmes rôles.
Quelle différence entre MVC et Laravel ?
MVC est une façon d'organiser le code ; Laravel est un framework qui l'applique et ajoute beaucoup d'outils. Les fichiers se correspondent presque un pour un : routes/web.php joue le rôle du Router, app/Http/Controllers/ celui des Controllers, resources/views/ celui des Views.
Où vérifier un formulaire : Controller ou View ?
Dans le Controller. La méthode store vérifie l'email avec filter_var et renvoie le formulaire avec un message s'il est invalide. La View se contente d'afficher ce message.
À retenir
- Le Model parle à MySQL et ne connaît ni le HTML ni
$_POST. - La View affiche, ne contient aucun SQL, et échappe les données avec
htmlspecialchars. - Le Controller reçoit la demande, valide, appelle le Model et choisit la View.
- Le Router fait entrer toutes les demandes par
public/index.phpet renvoie une 404 propre si la route n'existe pas. - Cette organisation est celle de Laravel et de Symfony.
Regarder le cours
Le cours complet (5 min 14, en darija) est sur sa page : Organiser son code avec MVC (E10).
- 0:00 Introduction : le code spaghetti
- 0:29 L'idée : 3 rôles, le restaurant, les dossiers
- 1:06 Le Model
- 1:51 La View
- 2:30 Le Controller
- 3:15 Le Router (front controller)
- 3:59 Le parcours d'une requête + requireLogin
- 4:38 Récap, Laravel et le défi
Pour aller plus loin
- Avant ce cours : le mini CRUD PHP + MySQL avec PDO (E05) et login et sessions en PHP (E09).
- La même organisation en JavaScript : Node.js et Express pour un développeur PHP.
- Pour passer à Laravel sans sauter les bases : le défi mini Laravel.
- Le parcours complet : Développement Web Full Stack.
- Apprendre avec l'IA sans perdre la main : coder avec ChatGPT sans copier-coller.
Sources
Quiz : 3 questions pour vérifier
Rien n’est envoyé : tes réponses restent dans ton navigateur.
Rédigé à partir du cours vidéo et de sa transcription, avec l’aide d’outils d’IA, relu et vérifié par Mehdi Laraibi le 4 novembre 2026.



