← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

MVC Pattern

Architecture PHP 5.0+ Beginner
debt(d7/e5/b7/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The term's detection_hints.tools is not specified. Fat controllers and logic-in-views are not caught by compilers or standard linters — the code runs fine syntactically. A specialist static analysis tool could flag method length or complexity, but no standard tool enforces MVC layer boundaries. Typically only careful code review surfaces the structural violation.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to move logic from controllers into Service classes or model methods — this is not a one-line patch. A fat controller with embedded validation, DB queries, email-sending, and formatting requires extracting multiple concerns into new classes, updating tests, and adjusting dependency injection — spanning several files even within one feature.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (e7). MVC applies to both web and cli contexts across the entire PHP application. Controllers are request entry-points for every feature; if fat-controller patterns are established early, every new feature inherits the pattern. Correcting the architecture later requires touching almost every controller and creating a new service layer — the structural choice shapes how every developer writes every action going forward.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field explicitly states that developers believe controllers should contain business logic because they handle requests — this is the 'obvious' intuition for most beginners and even intermediate developers. The correct behaviour (thin orchestrator) directly contradicts the natural mental model of 'the controller controls everything,' and the mistake is pervasive enough that the term's why_it_matters calls it 'the most common structural mistake in PHP applications.'

About DEBT scoring →

Also Known As

MVC Model View Controller model-view-controller fat model thin controller

TL;DR

Model-View-Controller — an architectural pattern separating data (Model), presentation (View), and request handling (Controller), the foundation of Laravel, Symfony, and most PHP frameworks.

Explanation

MVC separates an application into three layers: Model — represents data and business rules, queries the database, validates constraints, and enforces domain logic. View — renders the output (HTML, JSON, XML) from data provided by the controller. Controller — handles incoming requests, calls the appropriate model methods, and passes results to the view. The flow: HTTP request → Router → Controller → Model → Controller → View → HTTP response. PHP frameworks implement MVC with variations: Laravel uses Eloquent Active Record models, Blade view templates, and thin controllers; Symfony uses Doctrine Data Mapper models, Twig templates, and richer controllers. Common MVC violations: fat controllers (business logic in controllers), smart views (queries or logic in templates), and dumb models (models as mere database wrappers). The fat model / thin controller principle pushes business logic into models or dedicated service classes.

Common Misconception

Controllers should contain business logic because they handle requests. Controllers should be thin orchestrators — they receive a request, call a service or model method, and return a response. Business rules, validation logic, and data transformation belong in the Model layer or dedicated Service classes. A controller method exceeding 20 lines is usually a sign that business logic has leaked into it.

Why It Matters

MVC is the default architecture of every major PHP framework. Understanding it correctly prevents the most common structural mistake in PHP applications: fat controllers that become impossible to test because business logic is tightly coupled to the HTTP layer. A controller method that validates input, queries the database, sends emails, formats responses, and logs events cannot be unit tested without bootstrapping the entire framework. Separating these concerns into models and services makes each part independently testable and reusable.

Common Mistakes

  • Fat controllers — putting database queries, validation logic, and email sending directly in controller methods.
  • Logic in views — performing database queries or complex calculations in Blade/Twig templates.
  • Models as data bags — models that only have fillable/casts with no business methods are not using MVC correctly; models should enforce their own invariants.
  • One controller per table — controllers should be organised around user actions and HTTP resources, not database tables.

Code Examples

✗ Vulnerable
// Fat controller — untestable, all concerns mixed
class OrderController {
    public function store(Request $r) {
        if (!$r->user()->can('create-orders')) abort(403);
        $total = array_sum(array_column($r->items, 'price'));
        if ($total > 10000) throw new Exception('Order too large');
        $order = Order::create(['user_id' => $r->user()->id, 'total' => $total]);
        foreach ($r->items as $item) OrderItem::create([...]);
        Mail::to($r->user())->send(new OrderConfirmed($order));
        Slack::notify('#orders', 'New order: ' . $order->id);
        return response()->json($order, 201);
    }
}
✓ Fixed
// Thin controller — delegates to service
class OrderController {
    public function store(Request $r, OrderService $service) {
        $this->authorize('create', Order::class);
        $order = $service->createOrder($r->user(), $r->validated());
        return new OrderResource($order);
    }
}

// Service holds business logic — independently testable
class OrderService {
    public function createOrder(User $user, array $data): Order {
        $order = Order::createFromItems($user, $data['items']);
        OrderConfirmation::dispatch($order); // queued job
        return $order;
    }
}

Added 23 Mar 2026
Edited 30 May 2026
Views 54
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 2 pings T 3 pings F 0 pings S 0 pings S 4 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 10 Perplexity 6 Scrapy 5 SEMrush 4 Google 3 Ahrefs 3 Meta AI 2 Claude 2 Bing 2 ChatGPT 1 Majestic 1 PetalBot 1
crawler 37 crawler_json 3
DEV INTEL Tools & Severity
🔵 Info ⚙ Fix effort: Medium
⚡ Quick Fix
Move any logic in controllers that is not directly request/response handling into a Service class or model method — controllers should be 5–15 lines per action
📦 Applies To
PHP 5.0+ web cli

✓ schema.org compliant