MVC(Model-View-Controller)模式自 Smalltalk-80 發明以來,歷經 Struts、Spring MVC、Rails、Laravel 等框架演進,已成 Web 應用架構的事實標準。本文從 PHP 視角深度剖析 MVC 核心職責、為何選擇 MVC、並帶你從零手寫一個極簡 MVC 核心,理解框架底層運作機制。
| 角色 | 職責 | PHP 典型實現 |
|---|---|---|
| Model(模型) | 業務邏輯、資料存取、規則驗證 | Eloquent/Doctrine 實體、Repository、Service 類、資料庫抽象層(PDO 封裝) |
| View(視圖) | 資料呈現、模板渲染、前端資產 | Blade/Twig/Smarty 模板、JSON/API 資源類、原生 PHP 模板(include) |
| Controller(控制器) | 請求接收、參數驗證、協調 Model/View、回應 | 動作方法、表單請求驗證、中介軟體、資源控制器 |
爭議回應:有人質疑 HTTP 無狀態特性下 MVC「觀察者模式」失效。實則現代 PHP 框架透過「請求生命週期」模擬:每次請求重建 Controller,Model 狀態由資料庫/快取持久化,View 為一次性渲染,完全適配 Request/Response 模型。
以下代碼展示核心調度流程,省略路由解析、依賴注入、異常處理等生產級功能。
public/index.php<?php
require_once __DIR__ . '/../vendor/autoload.php';
use Core\Application;
use Core\Request;
$app = new Application();
$request = Request::capture();
$response = $app->handle($request);
$response->send();
Core/Application.phpnamespace Core;
class Application {
public function handle(Request $request): Response {
// 1. 路由匹配(簡化為解析 PATH_INFO)
$route = $this->resolveRoute($request->getPathInfo());
if (!$route) {
return new Response('Not Found', 404);
}
// 2. 實例化控制器並注入依賴
$controller = new $route['controller'](
new \App\Models\UserRepository(), // 手動注入,生產用容器
new \App\Views\TemplateEngine()
);
// 3. 呼叫動作方法
$method = $route['action'] . 'Action';
if (!method_exists($controller, $method)) {
return new Response('Action Not Found', 404);
}
return $controller->$method($request, $route['params']);
}
private function resolveRoute(string $path): ?array {
// 極簡路由表
$routes = [
'/' => ['controller' => \App\Controllers\HomeController::class, 'action' => 'index'],
'/user/{id}' => ['controller' => \App\Controllers\UserController::class, 'action' => 'show'],
];
// 實際專案請使用 FastRoute、League\Route 等路由庫
foreach ($routes as $pattern => $route) {
$regex = '#^' . str_replace('{id}', '(\d+)', $pattern) . '$#';
if (preg_match($regex, $path, $matches)) {
array_shift($matches);
return array_merge($route, ['params' => $matches]);
}
}
return null;
}
}
App/Controllers/BaseController.phpnamespace App\Controllers;
use Core\Request;
use Core\Response;
use App\Views\TemplateEngine;
abstract class BaseController {
protected TemplateEngine $view;
protected Request $request;
public function __construct(TemplateEngine $view, Request $request) {
$this->view = $view;
$this->request = $request;
}
protected function render(string $template, array $data = []): Response {
return new Response($this->view->render($template, $data));
}
protected function json(array $data, int $status = 200): Response {
return (new Response(json_encode($data, JSON_UNESCAPED_UNICODE), $status))
->header('Content-Type', 'application/json');
}
}
App/Controllers/UserController.phpnamespace App\Controllers;
use App\Models\UserRepository;
use Core\Request;
use Core\Response;
class UserController extends BaseController {
private UserRepository $users;
public function __construct(UserRepository $users, TemplateEngine $view, Request $request) {
parent::__construct($view, $request);
$this->users = $users;
}
public function showAction(Request $request, array $params): Response {
$id = (int)$params[0];
$user = $this->users->find($id);
if (!$user) {
return $this->json(['error' => 'User not found'], 404);
}
// API 回應
if ($request->wantsJson()) {
return $this->json($user->toArray());
}
// 網頁回應
return $this->render('user/profile', ['user' => $user]);
}
}
App/Models/UserRepository.phpnamespace App\Models;
use PDO;
class UserRepository {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function find(int $id): ?User {
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? new User($row) : null;
}
}
App/Views/TemplateEngine.phpnamespace App\Views;
class TemplateEngine {
public function render(string $template, array $data = []): string {
extract($data);
ob_start();
include __DIR__ . "/../templates/{$template}.php";
return ob_get_clean();
}
}
PHP-DI、Illuminate\Container 替代手動 new,支援自動配線。FastRoute 或 League\Route,支援 RESTful、中介軟體、群組、命名路由。Psr\Http\Message\ServerRequestInterface / ResponseInterface,符合 PSR-7,便於中介軟體管線。MVC 不是銀彈,但為 PHP 專案提供了經得起時間考驗的組織骨架。理解其核心調度流程(路由 → 控制器 → 模型 → 視圖 → 回應),才能在使用 Laravel、Symfony、ThinkPHP 等成熟框架時,不再被「魔法」困惑,而是能精準診斷效能瓶頸、擴充核心功能、做出架構決策。