Skip to content

PHP MVC Pattern — Complete Guide to Model-View-Controller Architecture

DodaTech Updated 2026-06-28 4 min read

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

Is MVC still relevant in 2026?

Yes. Most frameworks (Laravel, Symfony) use MVC. Modern variations add layers (services, repositories) but the core concept remains.

Can I use MVC without a framework?

Yes. MVC is a pattern, not a framework. You can implement it manually as shown above.

Where does form validation go in MVC?

Validation logic belongs in the model or a dedicated validation service. Controllers call validation before processing.

What is HMVC?

Hierarchical MVC allows nested MVC components. Each module has its own MVC triad. Used in modular applications.

How does Laravel implement MVC?

Laravel routes to controller methods, Eloquent models handle database, and Blade templates render views.

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