Zodra je PHP-project groter wordt dan een handjevol bestanden, loop je er vanzelf tegenaan: database-queries staan tussen je HTML, formulierverwerking zit verstopt in een template en niemand, ook jij zelf niet, snapt nog waar iets thuishoort. Het MVC pattern in PHP lost precies dat probleem op door je applicatie op te delen in drie duidelijke lagen.
In dit artikel leer je wat MVC is, waarom het werkt, en hoe je het zelf implementeert met plain PHP. Geen Laravel, geen Symfony, eerst het fundament, zodat je begrijpt wat frameworks voor je doen.
Wat is het MVC pattern?
MVC staat voor Model-View-Controller. Het is een architectuurpatroon dat in de jaren 70 ontstond bij Smalltalk en inmiddels dé standaard is voor webapplicaties. Het idee: splits je code op basis van verantwoordelijkheid.
- Model, beheert de data en de businesslogica. Denk aan database-interactie, validatie en berekeningen.
- View, de presentatielaag. HTML, templates, JSON-output voor een API.
- Controller, ontvangt de HTTP-request, coördineert wat er moet gebeuren en kiest de juiste View.
De flow is altijd hetzelfde: de browser stuurt een request naar een Controller, de Controller praat met het Model, en stuurt het resultaat naar een View die de response teruggeeft.
Waarom MVC?
Voordat we code gaan schrijven: waarom doe je dit eigenlijk? Drie redenen.
1. Scheiding van zorgen (separation of concerns). Een frontend-developer kan aan een View werken zonder je database-code te raken. Jij kunt een query aanpassen zonder HTML te breken.
2. Testbaarheid. Een Model zonder HTML erin kun je prima unit-testen. Probeer dat maar eens met een bestand vol echo statements tussen je SQL-queries.
3. Hergebruik. Dezelfde User-model kun je gebruiken in een webpagina, een JSON API en een CLI-script.
Als je nog onzeker bent over OOP: lees eerst object-oriented PHP basics. Classes en objecten zijn de bouwstenen van elk MVC-project.
De drie lagen in detail
Het Model
Een Model representeert een entiteit uit je domein, bijvoorbeeld een User, Product of BlogPost. Het is verantwoordelijk voor:
- Data ophalen uit en opslaan in de database
- Validatie van die data
- Businesslogica (bijvoorbeeld: "een gebruiker moet minstens 18 jaar zijn")
Belangrijk: het Model weet niets van HTTP, HTML of URLs. Het is puur je domein.
<?php
class User
{
public function __construct(
private PDO $db
) {}
public function find(int $id): ?array
{
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
return $user ?: null;
}
public function create(string $name, string $email): int
{
$stmt = $this->db->prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
);
$stmt->execute([$name, $email]);
return (int) $this->db->lastInsertId();
}
}
Gebruik je hier prepared statements? Ja, altijd. Zie werken met databases in PHP met PDO voor de achtergrond.
De View
Een View zet data om naar een presentatievorm. Voor een webpagina is dat HTML; voor een API is dat JSON. Een View bevat geen businesslogica, alleen de minimale logica om data weer te geven (een loop, een if-statement voor conditionele weergave).
<!-- views/user.php -->
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($user['name']) ?></title>
</head>
<body>
<h1><?= htmlspecialchars($user['name']) ?></h1>
<p>E-mail: <?= htmlspecialchars($user['email']) ?></p>
</body>
</html>
Merk op: htmlspecialchars() gebruiken we altijd bij output naar HTML. Dat voorkomt XSS-aanvallen en is een gewoonte die je jezelf meteen moet aanleren.
De Controller
De Controller is de dirigent. Hij:
- Ontvangt de request (URL, parameters, POST-data)
- Haalt de juiste data op via een Model
- Geeft die data door aan een View
- Stuurt de gerenderde output als response terug
<?php
class UserController
{
public function __construct(
private User $userModel
) {}
public function show(int $id): void
{
$user = $this->userModel->find($id);
if ($user === null) {
http_response_code(404);
require __DIR__ . '/../views/404.php';
return;
}
require __DIR__ . '/../views/user.php';
}
}
Zie je wat er niet gebeurt in de Controller? Geen SQL, geen HTML. Puur flow.
Een minimale MVC-applicatie in PHP
Tijd om het samen te brengen. We bouwen een piepkleine MVC-app met één routerende index.php, een Model, een View en een Controller.
Projectstructuur
my-app/
├── public/
│ └── index.php # front controller
├── src/
│ ├── Controllers/
│ │ └── UserController.php
│ └── Models/
│ └── User.php
├── views/
│ ├── user.php
│ └── 404.php
└── composer.json
De public/ map is je document root, alleen die is vanaf het web bereikbaar. Al je applicatiecode staat daarbuiten, veilig voor directe toegang.
De front controller
Elke request komt binnen via public/index.php. Dit bestand fungeert als front controller: het bepaalt welke Controller en methode aangeroepen worden.
<?php
require __DIR__ . '/../vendor/autoload.php';
$db = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass');
$userModel = new User($db);
$controller = new UserController($userModel);
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if (preg_match('#^/users/(\d+)$#', $path, $matches)) {
$controller->show((int) $matches[1]);
} else {
http_response_code(404);
require __DIR__ . '/../views/404.php';
}
Dit is routing in zijn simpelste vorm. In echte applicaties gebruik je een routing-library (zoals nikic/fast-route), maar het principe blijft identiek: een URL wordt gekoppeld aan een Controller-methode.
Autoloading regel je via Composer. Zie Composer en dependency management in PHP als je die nog niet hebt ingesteld.
Veelgemaakte fouten
Fat Controllers. Controllers die honderden regels lang zijn en zelf SQL schrijven of business-regels bevatten. Oplossing: verplaats die logica naar Models of losse service-classes.
Logic in Views. Als je View complexe if/else-bomen of database-queries bevat, is je scheiding kapot. Views mogen alleen weergavelogica hebben.
Models die HTML genereren. Klassiek antipatroon. Een User-model dat een renderProfile() methode heeft die HTML teruggeeft, heeft zijn verantwoordelijkheid verwarrend.
Globale state. Gebruik geen $_GET of $_POST direct in Models of Views. Dat hoort in de Controller, die de waarden doorgeeft. Herhaal waar nodig je kennis van functies en scope, dependency injection is de nette manier om afhankelijkheden expliciet te maken.
MVC en moderne PHP-frameworks
De meeste PHP-developers schrijven geen eigen MVC-framework meer. Je gebruikt Laravel of Symfony, die het patroon volledig implementeren plus tientallen features eromheen (routing, middleware, templating, ORM, dependency injection).
Maar: begrijpen hoe MVC werkt onder de motorkap is onmisbaar. Als je weet dat een Eloquent-model in Laravel de M is, een Blade-template de V en een Controller de C, dan val je veel sneller in bij een bestaand project.
Wil je je verdiepen in de theorie? Martin Fowler's artikel over GUI Architectures is nog altijd de gouden standaard voor de verschillen tussen MVC, MVP en MVVM.
Wanneer MVC overkill is
Eerlijk is eerlijk: MVC is niet altijd de juiste keuze. Voor een contactformulier van 20 regels heb je geen drie lagen nodig. Voor een klein script dat een CSV verwerkt ook niet.
MVC begint te renderen zodra je applicatie:
- Meer dan een paar pagina's heeft
- Een database gebruikt
- Door meerdere developers onderhouden wordt
- Langer dan een paar maanden meegaat
Voor alles daaronder: houd het simpel. Overengineering is net zo erg als underengineering.
Van hier naar productie
Heb je de basis van MVC door? Mooi. De volgende stappen om er een productiewaardige applicatie van te maken:
- Dependency injection container, zodat je geen
newstatements in je front controller hoeft te schrijven - Templating engine zoals Twig of Blade, meer veiligheid en minder PHP-tags in views
- Request/Response objecten volgens PSR-7, in plaats van direct
$_GETenecho - Middleware, om cross-cutting concerns zoals authenticatie centraal te regelen
- Een ORM, zoals Eloquent of Doctrine, voor complexere datamodellen
Elk van deze stappen is een artikel waard. Maar alles bouwt voort op het fundament dat je nu hebt: Model, View, Controller.
Veelgestelde vragen
Wat is het MVC pattern in PHP?
MVC staat voor Model-View-Controller, een architectuurpatroon dat een applicatie opdeelt in drie verantwoordelijkheden: data (Model), presentatie (View) en logica (Controller). Het maakt PHP-code overzichtelijker, testbaarder en beter onderhoudbaar.
Waarom zou ik MVC gebruiken in plaats van procedurele PHP?
MVC dwingt scheiding van zorgen af, waardoor je HTML niet vermengt met database-queries of businesslogica. Dat maakt je code makkelijker te onderhouden, hergebruiken en testen, zeker naarmate je applicatie groeit.
Moet ik een framework gebruiken voor MVC?
Nee, je kunt MVC prima zelf implementeren met plain PHP en OOP. Voor grotere projecten zijn frameworks als Laravel of Symfony handiger omdat ze routing, templating en dependency injection kant-en-klaar bieden.
Wat is het verschil tussen een Controller en een Model?
De Controller ontvangt de request, coördineert de flow en bepaalt welke response wordt teruggegeven. Het Model bevat de data en de businesslogica, zoals database-interactie en validatie. Een Controller gebruikt een Model, nooit andersom.
Is MVC het enige architectuurpatroon voor PHP?
Nee, er zijn varianten zoals MVP, MVVM en hexagonale architectuur. MVC is het populairst in de PHP-wereld omdat vrijwel alle grote frameworks erop gebouwd zijn, maar voor complexere domeinen kiezen developers soms voor Domain-Driven Design.