Cours9 min de lecture

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 : العربية

Mehdi en tenue de formateur, couverture de l’article : MVC en PHP

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
<?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 :

text
app/
  Models/User.php
  Views/users/index.php
  Controllers/UserController.php
config/db.php
public/index.php      ← la seule porte d'entrée du site

Le 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
<?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 :

php
<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
<?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 :

php
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 :

apache
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
<?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

  1. Le navigateur demande /users.
  2. public/index.php la reçoit ; le Router l'envoie à UserController::index().
  3. index() demande les utilisateurs au Model User.
  4. Le Model fait un SELECT dans MySQL et renvoie le résultat.
  5. Le Controller transmet $users à la View.
  6. 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 :

php
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ôleDans ce coursDans Laravel
Modelapp/Models/User.phpapp/Models/User.php
Viewapp/Views/users/resources/views/users/
Controllerapp/Controllers/app/Http/Controllers/
Routerpublic/index.phproutes/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.php et 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

Sources

Quiz : 3 questions pour vérifier

Rien n’est envoyé : tes réponses restent dans ton navigateur.

  1. Dans une application MVC, quel fichier doit contenir la requête SELECT * FROM users ?
  2. À quoi sert public/index.php dans le cours ?
  3. Pourquoi htmlspecialchars est-il appelé dans la View ?

Partager

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.