在 PHP 中落地 MVC,核心在於建立清晰的請求處理管線:前端控制器 → 路由分發 → 控制器動作 → 服務層/資料層 → 視圖渲染 → 回應。本文從零構建一個極簡 MVC 骨架,展示核心組件實作。
public/
index.php # 前端控制器 (唯一入口)
.htaccess # URL 重寫
src/
Core/
Application.php # 應用核心、服務容器
Router.php # 路由分發
Controller.php # 基礎控制器
View.php # 視圖渲染
Database.php # PDO 封裝
Controllers/
HomeController.php
UserController.php
Models/
User.php # 領域模型/實體
Repositories/
UserRepository.php
Views/
home.php
user/
show.php
index.php
config/
routes.php # 路由定義
database.php # DB 配置
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Core\Application;
$app = new Application([
'routes' => require __DIR__ . '/../config/routes.php',
'db' => require __DIR__ . '/../config/database.php',
]);
$app->run();
?>
<?php
namespace App\Core;
class Router {
private array $routes = [];
public function __construct(array $routes) { $this->routes = $routes; }
public function dispatch(string $method, string $uri): void {
$uri = parse_url($uri, PHP_URL_PATH);
$routeKey = "$method:$uri";
// 靜態路由優先
if (isset($this->routes[$routeKey])) {
$this->execute($this->routes[$routeKey]);
return;
}
// 動態路由匹配 (簡易版)
foreach ($this->routes as $pattern => $handler) {
if ($this->matchRoute($pattern, $uri, $method)) {
$this->execute($handler, $this->extractParams($pattern, $uri));
return;
}
}
http_response_code(404);
echo "404 Not Found";
}
private function matchRoute(string $pattern, string $uri, string $method): bool {
$pattern = preg_replace('/\{(\w+)\}/', '([^/]+)', $pattern);
$pattern = "^" . $pattern . "$";
return (bool) preg_match("#$pattern#", $uri) && str_starts_with($pattern, $method . ':');
}
private function extractParams(string $pattern, string $uri): array {
preg_match_all('/\{(\w+)\}/', $pattern, $keys);
$pattern = preg_replace('/\{(\w+)\}/', '([^/]+)', $pattern);
preg_match("#^$pattern$#", $uri, $matches);
array_shift($matches);
return array_combine($keys[1], $matches);
}
private function execute(array $handler, array $params = []): void {
[$controllerClass, $method] = $handler;
$controller = new $controllerClass();
call_user_func_array([$controller, $method], $params);
}
}
?>
<?php
// Core/Controller.php
namespace App\Core;
class Controller {
protected function view(string $template, array $data = []): void {
View::render($template, $data);
}
protected function json(array $data, int $status = 200): void {
http_response_code($status);
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
// Core/View.php
namespace App\Core;
class View {
public static function render(string $template, array $data = []): void {
extract($data); // 變數解壓到當前作用域
$file = __DIR__ . "/../../Views/$template.php";
if (!is_file($file)) throw new Exception("View not found: $template");
include $file;
}
}
?>
<?php
namespace App\Repositories;
use App\Core\Database;
use App\Models\User;
class UserRepository {
public function __construct(private Database $db) {}
public function findById(int $id): ?User {
$row = $db->fetchOne('SELECT * FROM users WHERE id = ?', [$id]);
return $row ? $this->hydrate($row) : null;
}
public function findAll(int $limit = 20, int $offset = 0): array {
$rows = $db->fetchAll('SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?', [$limit, $offset]);
return array_map([$this, 'hydrate'], $rows);
}
public function create(array $data): int {
return $db->insert('users', $data);
}
private function hydrate(array $row): User {
return new User(
$row['id'],
$row['name'],
$row['email'],
new DateTime($row['created_at'])
);
}
}
?>
<?php
namespace App\Controllers;
use App\Core\Controller;
use App\Repositories\UserRepository;
class UserController extends Controller {
public function index(UserRepository $users): void {
$page = max(1, (int)($_GET['page'] ?? 1));
$userList = $users->findAll(15, ($page - 1) * 15);
$this->view('user/index', ['users' => $userList, 'page' => $page]);
}
public function show(UserRepository $users, int $id): void {
$user = $users->findById($id);
if (!$user) { http_response_code(404); echo 'User not found'; return; }
$this->view('user/show', ['user' => $user]);
}
public function store(UserRepository $users): void {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); return; }
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
if ($name && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$id = $users->create(['name' => $name, 'email' => $email]);
header("Location: /users/$id");
} else {
$this->view('user/create', ['errors' => ['name' => '必填', 'email' => '格式錯誤']]);
}
}
}
?>
<?php
return [
'GET:/' => [\App\Controllers\HomeController::class, 'index'],
'GET:/users' => [\App\Controllers\UserController::class, 'index'],
'GET:/users/{id:\d+}' => [\App\Controllers\UserController::class, 'show'],
'GET:/users/create' => [\App\Controllers\UserController::class, 'create'],
'POST:/users' => [\App\Controllers\UserController::class, 'store'],
];
?>
這個骨架雖簡陋,但包含 MVC 核心循環。理解其運作,才能駕馭 Laravel/Symfony 等全功能框架。