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.