PHP MVC Pattern — Complete Guide to Model-View-Controller Architecture
In this tutorial, you will learn about PHP MVC Pattern. We cover key concepts, practical examples, and best practices to help you master this topic.
MVC (Model-View-Controller) separates PHP applications into three interconnected components: models handle data, views handle presentation, and controllers handle request logic.
What You'll Learn
By the end of this tutorial, you'll implement MVC architecture, create models, views, and controllers, build a front controller router, and organize PHP projects following MVC conventions.
Why MVC Matters
MVC separates concerns making code more maintainable, testable, and organized. Each component has a single responsibility, enabling parallel development and clear separation of business logic from presentation.
Real-World Use
Laravel uses MVC: Controllers handle HTTP requests, Models interact with the database via Eloquent, and Blade templates render views. Developers work on different layers without conflicts.
MVC Learning Path
flowchart LR
A[PDO] --> B[MVC]
B --> C[REST API]
C --> D[JWT Auth]
D --> E[Testing]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Front Controller (Router)
<?php
// index.php (entry point)
require __DIR__ . "/vendor/autoload.php";
$uri = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$method = $_SERVER["REQUEST_METHOD"];
$routes = [
"GET" => [
"/" => "HomeController@index",
"/users" => "UserController@index",
"/users/(\d+)" => "UserController@show",
],
"POST" => [
"/users" => "UserController@store",
],
];
foreach ($routes[$method] ?? [] as $pattern => $handler) {
if (preg_match("#^$pattern$#", $uri, $matches)) {
[$controller, $action] = explode("@", $handler);
$controller = "App\\Controllers\\$controller";
array_shift($matches);
echo (new $controller())->$action(...$matches);
exit;
}
}
http_response_code(404);
echo "404 Not Found";
Model
<?php
// src/Models/User.php
namespace App\Models;
use PDO;
class User {
private PDO $db;
public function __construct(PDO $db) { $this->db = $db; }
public function all(): array {
return $this->db->query("SELECT * FROM users")->fetchAll();
}
public function find(int $id): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$result = $stmt->fetch();
return $result ?: null;
}
public function create(array $data): int {
$stmt = $this->db->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$data["name"], $data["email"]]);
return (int)$this->db->lastInsertId();
}
}
Controller
<?php
// src/Controllers/UserController.php
namespace App\Controllers;
use App\Models\User;
class UserController {
private User $userModel;
public function __construct() {
$this->userModel = new User(Database::getConnection());
}
public function index(): string {
$users = $this->userModel->all();
ob_start();
include __DIR__ . "/../Views/users/index.php";
return ob_get_clean();
}
public function show(int $id): string {
$user = $this->userModel->find($id);
if (!$user) {
http_response_code(404);
return "User not found";
}
ob_start();
include __DIR__ . "/../Views/users/show.php";
return ob_get_clean();
}
}
View
<?php
<!-- src/Views/users/index.php -->
<h1>Users</h1>
<ul>
<?php foreach ($users as $user): ?>
<li><?= htmlspecialchars($user["name"]) ?> - <?= htmlspecialchars($user["email"]) ?></li>
<?php endforeach; ?>
</ul>
<!-- src/Views/users/show.php -->
<h1><?= htmlspecialchars($user["name"]) ?></h1>
<p>Email: <?= htmlspecialchars($user["email"]) ?></p>
Common Mistakes
1. Fat Controllers
Putting all logic in controllers violates MVC. Keep controllers thin — they only coordinate between models and views.
2. Business Logic in Views
Views should only display data, not Process it. Don't query databases or modify data in view files.
3. No Separation Of Concerns
A class that handles HTTP, business logic, and database access does too much. Split into controller, service, and model layers.
4. Not Using a Front Controller
Each PHP file as an entry point is messy. Use a single index.php that routes all requests.
5. Hardcoding Dependencies in Controllers
Use Dependency Injection to provide models/services to controllers rather than creating them inside.
Practice Questions
1. What does each MVC component do?
Model: data and business logic. View: presentation (HTML). Controller: handles requests and coordinates model/view.
2. What is a front controller?
A single PHP file (index.php) that handles all incoming requests and routes them to the appropriate controller.
3. Why keep controllers thin?
Thin controllers are easier to test and maintain. Business logic belongs in models or service classes.
4. How does routing work in MVC?
The router parses the URL, matches it against defined patterns, and calls the appropriate controller method.
5. Challenge: Create an MVC router that supports dynamic URL parameters.
<?php
class Router {
private array $routes = [];
public function get(string $pattern, callable $handler): void {
$this->routes["GET"][] = ["pattern" => $pattern, "handler" => $handler];
}
public function dispatch(string $method, string $uri): void {
foreach ($this->routes[$method] ?? [] as $route) {
$regex = preg_replace("/\{(\w+)\}/", "(?P<$1>[^/]+)", $route["pattern"]);
if (preg_match("#^$regex$#", $uri, $matches)) {
$params = array_filter($matches, "is_string", ARRAY_FILTER_USE_KEY);
echo call_user_func($route["handler"], $params);
return;
}
}
http_response_code(404);
echo "404";
}
}
$router = new Router();
$router->get("/users/{id}", fn($p) => "User ID: " . $p["id"]);
$router->dispatch($_SERVER["REQUEST_METHOD"], parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));
FAQ
Mini Project: Simple MVC Framework
Build a minimal MVC framework with routing, models, and views.
<?php
// index.php
require "vendor/autoload.php";
$router = new App\Core\Router();
$router->get("/", "HomeController@index");
$router->get("/users", "UserController@index");
$router->dispatch();
// app/Controllers/HomeController.php
namespace App\Controllers;
class HomeController {
public function index(): string {
return "<h1>Welcome to MVC</h1>";
}
}
What's Next
PHP REST API PHP JWT Auth PHP Testing PHPUnit
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro