MVC Architecture
The structure behind every serious PHP app — separating data (Model), pages (View) and logic (Controller), with a router sending each URL to the right place. This is the mental model Laravel is built on.
What you will learn
- Explain the Model, View and Controller roles
- Understand the request lifecycle and routing
- Build a minimal MVC flow by hand
Why structure matters
Your projects so far mix everything together — database queries, HTML and logic all in one .php file. That works for tiny scripts but becomes a tangled mess as an app grows. MVC is the industry-standard way to organise a web app by separating concerns into three parts. Understanding MVC is the single biggest step toward thinking like a framework — because Laravel is MVC.
| Part | Responsibility | Example |
|---|---|---|
| Model | The data and database logic | A User class that fetches users |
| View | The HTML the user sees | A users.php template |
| Controller | The logic that ties them together | Get users from the Model, pass to the View |
The golden rule: the Model never prints HTML, the View never queries the database, and the Controller never contains raw HTML or SQL — each part has one job.
The request lifecycle
In an MVC app, every request follows the same path. A router is the traffic controller that reads the URL and decides which Controller method should handle it.
- A request comes in for a URL like
/users. - The router looks up that URL and calls the matching Controller method.
- The Controller asks the Model for the data it needs (e.g. all users).
- The Model queries the database and returns the data.
- The Controller hands that data to a View.
- The View fills in the HTML and the finished page is sent back.
A Model
The Model owns the data. It talks to the database and returns plain PHP data — never HTML. Here is a UserModel that fetches users (using the PDO you already know):
<?php
namespace App\Models;
class UserModel {
public function __construct(private \PDO $db) {}
public function all(): array {
$stmt = $this->db->query("SELECT * FROM users");
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}
?>The UserModel receives a PDO connection in its constructor and exposes one method, all(), which runs the query and returns the rows as an array. Notice there is no echo and no HTML anywhere — the Model’s only job is to get and return data. Anything that wants users calls ->all() and gets a clean array back.
Note: No direct output — the Model is a supplier of data. Keeping it HTML-free means you could reuse the exact same UserModel in a web page, an API or a command-line script.
A Controller and a View
The Controller is the coordinator: it calls the Model for data, then loads a View (a template file) and passes the data into it. The View is almost pure HTML with small holes for the data.
<?php
// src/Controllers/UserController.php
namespace App\Controllers;
use App\Models\UserModel;
class UserController {
public function __construct(private UserModel $model) {}
public function index(): void {
$users = $this->model->all(); // get data from the Model
require "views/users.php"; // hand it to the View
}
}
?>The index() method does two clear things: it asks the Model for $users, then loads the View file views/users.php. Because that View is included after $users exists, the View can simply use the $users variable. The Controller holds no HTML and no SQL — it only *coordinates*.
<!-- views/users.php — almost pure HTML -->
<h1>Users</h1>
<ul>
<?php foreach ($users as $user): ?>
<li><?= htmlspecialchars($user["name"]) ?></li>
<?php endforeach; ?>
</ul>The View loops over the $users array the Controller provided and prints each name inside a list item. <?= ... ?> is shorthand for <?php echo ... ?>, and htmlspecialchars keeps it safe. There is no database code here — the View only *displays* data it was handed.
Note: Output (in the browser, with three users): Users • Asha • Ravi • Meera Three files, three jobs: the Model fetched the data, the Controller coordinated, the View displayed. Change the database and you touch only the Model; redesign the page and you touch only the View. That separation is the whole point.
The router (the front door)
Finally, a simple router maps each URL to a Controller method. Real apps send every request through one entry file (index.php) which consults the router.
<?php
// a tiny router
$routes = [
"/users" => [UserController::class, "index"],
"/" => [HomeController::class, "index"],
];
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
if (isset($routes[$path])) {
[$controller, $method] = $routes[$path];
(new $controller(/* ...dependencies... */))->$method();
} else {
http_response_code(404);
echo "Page not found";
}
?>The $routes array maps each URL path to a [Controller, method] pair. parse_url(...) pulls the path (like /users) out of the incoming request. If that path is in the table, we create the matching Controller and call its method; if not, we send a 404 "not found". This is exactly what Laravel’s routing does, just far more powerfully.
Note: Output (visiting /users):
the Users page from the View above
Visiting an unknown URL like /xyz instead prints Page not found with a 404 status. One front door, a lookup table, and each URL flows to the right Controller → Model → View. You have just built the skeleton of a framework.
Tip: You will never hand-write all this in a real job — Laravel gives you routing, Models (Eloquent), Controllers and Views (Blade) ready-made. But because you understand the MVC flow underneath, Laravel will feel obvious instead of magic.
Q. In MVC, which part is responsible for talking to the database?
✍️ Practice
- Split one of your earlier mixed PHP pages into a Model, a Controller and a View.
- Write a tiny
$routesarray that maps two URLs to two controller methods.
🏠 Homework
- Build a minimal MVC for a
postsresource: a PostModel (fetch all), a PostController (index), a view, and a router that serves/posts.