MVC Pattern
Also Known As
TL;DR
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
Why It Matters
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
// 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);
}
}
// 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;
}
}