Mvc Framework

By the end of this lesson you'll have built a tiny MVC app from scratch — a front controller, a router, a controller, a PDO-backed model, and a view — and you'll understand exactly what Laravel and Symfony are doing under the hood.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ The Problem: Everything in One File

Before you build the pattern, feel the pain it removes. A beginner PHP page often does everything in one file: it reads the URL, opens a database, runs a query, and prints HTML — all tangled together. It works for one page, but the moment the app grows, every change risks breaking something unrelated. The job of MVC (Model-View-Controller) is to split that one file into three parts, each with a single responsibility.

Notice the trap: the SQL, the input handling, and the echo of HTML are all in the same breath. Change the database and you might break the markup; restyle the page and you might break the query. The next three sections pull these apart into a Model , a View , and a Controller .

2️⃣ The Model — Data & Rules (PDO)

The Model owns your data and your business rules. It's the only layer that talks to the database, and it does so through PDO — PHP's built-in database toolkit. Crucially, a model returns plain PHP arrays or objects; it never echoes HTML. Notice the prepared statement below: the value goes in through a :id placeholder, never glued into the SQL string. That single habit is what stops SQL-injection attacks.

The model knows nothing about web pages. You could call find() from a web request, a command-line script, or a test — and it behaves identically. That reusability is the whole point of keeping data access in its own layer.

3️⃣ The View — Presentation Only

The View does exactly one thing: turn data into HTML. It receives data the controller has already prepared and renders it — no queries, no business rules, no decisions. The pattern below uses output buffering ( ob_start / ob_get_clean ) to capture a template's output as a string, and htmlspecialchars() to escape values so a malicious name can't inject markup. Escaping on output is the view's one security responsibility.

In a real project the template is a separate file like app/Views/user.php , and full frameworks swap this hand-rolled renderer for a template engine such as Blade (Laravel) or Twig (Symfony). The idea is identical: data in, HTML out, nothing else.

4️⃣ The Router & Controller — Wiring It Together

Now the part that makes it an application. A front controller is a single entry point — in production it's public/index.php , and the web server rewrites every URL to it. Inside it, a router reads the request URL (from $_SERVER['REQUEST_URI'] ) and matches it against registered routes, capturing dynamic parts like :id . It then calls the matching Controller method — the thin coordinator that reads input, asks the Model for data, and hands that data to a View. The controller holds no SQL and no HTML; it just wires the other layers together.

Trace one request: the router turns /users/2 into a regex match, captures 2 , and calls UserController::show("2") , which returns the profile string. That four-step flow — URL → router → controller → response — is the heartbeat of every MVC framework.

Now you try. The router and controller are written for you below — you only register one route and fire one request. Fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

5️⃣ Autoloading — Loading Classes Automatically

So far the classes lived in one file. In a real project each class gets its own file, and writing require_once for every one is tedious and fragile. Autoloading fixes this: you register a function with spl_autoload_register() , and PHP calls it automatically the first time it meets a class it hasn't loaded yet. The function maps the class name to a file path and requires it — just in time.

In practice you don't write this by hand. Composer generates it for you: declare a PSR-4 mapping in composer.json (the convention that maps the App\ namespace to the app/ folder), run composer dump-autoload , and add a single require "vendor/autoload.php"; at the top of your front controller. Every class in your project then loads itself.

One more guided exercise — this time on the Model. Finish the safe lookup so the value flows through a prepared statement, never into the SQL string.

6️⃣ How Laravel & Symfony Build On This

You've now built every core piece a framework has — just smaller. Full frameworks keep this exact shape and add the hard parts you don't want to hand-roll: a powerful router, dependency injection (objects are constructed and passed in for you), an ORM like Eloquent or Doctrine (models map to tables without raw SQL), a template engine (Blade, Twig), plus validation, sessions, and security defaults. The mental model you just built — front controller → router → controller → model → view — is exactly how a Laravel or Symfony request flows. Reading their source code will now feel familiar rather than magical.

📋 Quick Reference — MVC in PHP

No code is filled in this time — just a brief and an outline. Build a tiny MVC app with a model, a controller, and the router from the worked example. Run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This is the write-run-check loop you'll use on every real feature.

Practice quiz

What do the three letters in MVC stand for?

  • Module-Variable-Class
  • Method-Value-Callback
  • Model-View-Controller
  • Map-Validate-Cache

Answer: Model-View-Controller. MVC means Model (data & rules), View (HTML), and Controller (the thin coordinator).

Which MVC layer is the ONLY one that talks to the database?

  • The Model
  • The View
  • The Controller
  • The Router

Answer: The Model. The Model owns data and business rules, querying the database via PDO and returning plain arrays — it never echoes HTML.

What is the single responsibility of the View?

  • Run database queries
  • Decide which route matched
  • Hash passwords
  • Turn data the controller prepared into HTML, and nothing else

Answer: Turn data the controller prepared into HTML, and nothing else. The View only renders data into HTML (escaping it on output); it does no queries and makes no decisions.

What is a 'front controller' in this MVC design?

  • The first controller class alphabetically
  • A single entry point (public/index.php) that every request is routed to, which boots the app and dispatches
  • A controller that only handles the home page
  • The View that renders the front page

Answer: A single entry point (public/index.php) that every request is routed to, which boots the app and dispatches. The front controller is the one file every URL is rewritten to; it boots the app once and dispatches to the right controller.

How does the Model stop SQL-injection attacks?

  • By using a prepared statement with a :id placeholder, so user input never goes into the SQL string
  • By escaping HTML with htmlspecialchars()
  • By validating that input is lowercase
  • By hashing the input first

Answer: By using a prepared statement with a :id placeholder, so user input never goes into the SQL string. The value goes in through a bound :id placeholder, never glued into the SQL — that single habit stops injection.

What does the View use to escape data so a name like <script> can't inject markup?

  • strip_tags()
  • addslashes()
  • htmlspecialchars()
  • urlencode()

Answer: htmlspecialchars(). htmlspecialchars() escapes output — the View's one security job — so injected markup is shown as text, not executed.

What is the role of the Controller in this architecture?

  • Hold all the SQL queries
  • Be the thin coordinator: read input, call the Model, hand data to a View, and return the result
  • Render the final HTML directly
  • Store the database credentials

Answer: Be the thin coordinator: read input, call the Model, hand data to a View, and return the result. The Controller wires the layers together; it holds no SQL and no HTML — a fat controller has stolen the Model's job.

What does spl_autoload_register() do?

  • Automatically connects to the database
  • Generates HTML for every page
  • Caches query results
  • Registers a function PHP calls to load a class's file the first time the class is referenced

Answer: Registers a function PHP calls to load a class's file the first time the class is referenced. It hands PHP a function that maps a class name to a file path and loads it just in time, replacing manual require_once calls.

What does Composer's PSR-4 convention map?

  • A URL to a controller method
  • A namespace prefix to a folder, e.g. App\ to app/, so App\Models\UserModel lives at app/Models/UserModel.php
  • A database table to a class
  • A locale to a translation file

Answer: A namespace prefix to a folder, e.g. App\ to app/, so App\Models\UserModel lives at app/Models/UserModel.php. PSR-4 maps a namespace to a directory; Composer reads the psr-4 block and generates the whole autoloader for you.

How do full frameworks like Laravel and Symfony relate to this micro-framework?

  • They use a completely different, unrelated architecture
  • They replace MVC with procedural code
  • They keep this exact shape (front controller → router → controller → model → view) and add DI, an ORM, a template engine, and security
  • They avoid routers entirely

Answer: They keep this exact shape (front controller → router → controller → model → view) and add DI, an ORM, a template engine, and security. Laravel and Symfony build on this same pattern, adding the hard parts you don't want to hand-roll.