Building Real ApplicationsPro· 50 min read

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.

PartResponsibilityExample
ModelThe data and database logicA User class that fetches users
ViewThe HTML the user seesA users.php template
ControllerThe logic that ties them togetherGet 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.

  1. A request comes in for a URL like /users.
  2. The router looks up that URL and calls the matching Controller method.
  3. The Controller asks the Model for the data it needs (e.g. all users).
  4. The Model queries the database and returns the data.
  5. The Controller hands that data to a View.
  6. 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):

Model: only data, no HTML
<?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.

Controller: get data, choose a View
<?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*.

View: only HTML, with small PHP holes
<!-- 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.

A minimal 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?

Answer: The Model owns data and database logic. The View shows HTML, the Controller coordinates, and the router maps URLs to Controllers.

✍️ Practice

  1. Split one of your earlier mixed PHP pages into a Model, a Controller and a View.
  2. Write a tiny $routes array that maps two URLs to two controller methods.

🏠 Homework

  1. Build a minimal MVC for a posts resource: a PostModel (fetch all), a PostController (index), a view, and a router that serves /posts.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →