MVC(Model-View-Controller)是现代 Web 开发中最常见的架构模式之一。它将应用程序分为三个核心部分:模型负责数据逻辑,视图负责展示,控制器负责协调两者。虽然市面上有 Laravel、Symfony 等成熟框架,但从零实现一个简易 MVC 框架,能帮助我们更深刻地理解框架的运作机制。本文将从路由解析、控制器调度和视图渲染三个环节,构建一个可用的迷你 MVC 框架。
一、整体目录结构
一个清晰的目录结构是框架的基础。我们采用如下组织方式:
/mini-mvc
├── public/
│ └── index.php // 唯一入口文件
├── app/
│ ├── Controllers/
│ │ └── HomeController.php
│ └── Views/
│ └── home.php
├── core/
│ ├── Router.php // 路由解析
│ ├── Controller.php // 控制器基类
│ └── View.php // 视图渲染
└── routes.php // 路由定义
所有请求都通过 public/index.php 进入,由它加载核心组件并启动框架。
二、入口文件与自动加载
入口文件负责定义常量、注册自动加载,并启动路由。
<?php
// public/index.php
define('BASE_PATH', dirname(__DIR__));
spl_autoload_register(function ($class) {
$file = BASE_PATH . '/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) {
require $file;
}
});
require BASE_PATH . '/routes.php';
use Core\Router;
Router::dispatch($_SERVER['REQUEST_URI']);
这里使用 spl_autoload_register 实现简单的 PSR-4 风格自动加载:将命名空间中的反斜杠转换为目录分隔符,再拼接 .php 后缀。这样 Core\Router 会自动加载 core/Router.php。
三、路由解析
路由的核心任务是将 URL 映射到具体的控制器方法。我们支持形如 /user/show/1 的路径,并允许通过占位符捕获参数。
<?php
// core/Router.php
namespace Core;
class Router
{
private static array $routes = [];
public static function get(string $path, array $action): void
{
self::$routes['GET'][$path] = $action;
}
public static function post(string $path, array $action): void
{
self::$routes['POST'][$path] = $action;
}
public static function dispatch(string $uri): void
{
$uri = parse_url($uri, PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
foreach (self::$routes[$method] ?? [] as $route => $action) {
// 将 /user/{id} 转换为正则
$pattern = preg_replace('#\{(\w+)\}#', '(?P<$1>[^/]+)', $route);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
[$controller, $methodName] = $action;
$instance = new $controller();
$instance->$methodName(...array_values($params));
return;
}
}
http_response_code(404);
echo '404 Not Found';
}
}
路由定义文件 routes.php 如下:
<?php
use App\Controllers\HomeController;
Router::get('/', [HomeController::class, 'index']);
Router::get('/user/{id}', [HomeController::class, 'show']);
当访问 /user/5 时,正则匹配会捕获 id=5,并作为参数传给 HomeController::show(5)。
四、控制器基类与业务控制器
控制器基类封装视图渲染方法,让子类可以直接调用 $this->render()。
<?php
// core/Controller.php
namespace Core;
abstract class Controller
{
protected function render(string $view, array $data = []): void
{
View::render($view, $data);
}
}
业务控制器继承它:
<?php
// app/Controllers/HomeController.php
namespace App\Controllers;
use Core\Controller;
class HomeController extends Controller
{
public function index(): void
{
$this->render('home', [
'title' => '欢迎来到迷你 MVC',
'message' => '这是由 PHP 手写的简易框架。',
]);
}
public function show(int $id): void
{
$this->render('home', [
'title' => "用户详情 #{$id}",
'message' => "你正在查看 ID 为 {$id} 的用户。",
]);
}
}
控制器只负责组织数据并选择视图,不掺杂 HTML,职责清晰。
五、视图渲染
视图层需要解决两个问题:一是将数据注入模板,二是支持模板嵌套或布局。这里采用“提取变量 + 输出缓冲”的方式。
<?php
// core/View.php
namespace Core;
class View
{
public static function render(string $view, array $data = []): void
{
$file = BASE_PATH . '/app/Views/' . $view . '.php';
if (!file_exists($file)) {
throw new \RuntimeException("视图文件不存在: {$view}");
}
extract($data, EXTR_SKIP);
ob_start();
require $file;
$content = ob_get_clean();
// 如果有布局文件,则注入内容
$layout = BASE_PATH . '/app/Views/layout.php';
if (file_exists($layout)) {
require $layout;
} else {
echo $content;
}
}
}
extract() 将数组键名转为变量名,ob_start() 开启输出缓冲,捕获视图内容后再交给布局文件。对应的视图文件:
<?php // app/Views/home.php ?>
<h1><?= htmlspecialchars($title) ?></h1>
<p><?= htmlspecialchars($message) ?></p>
布局文件:
<?php // app/Views/layout.php ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($title ?? 'Mini MVC') ?></title>
</head>
<body>
<main><?= $content ?></main>
</body>
</html>
注意输出时使用 htmlspecialchars 转义,避免 XSS 攻击。
六、运行与扩展方向
在项目根目录执行 php -S localhost:8000 -t public,访问 http://localhost:8000/ 和 http://localhost:8000/user/5,即可看到路由、控制器与视图协同工作的效果。
这个框架虽然只有百余行,但已经具备 MVC 的核心特征。后续可以从以下方向扩展:
- 模型层:引入 PDO 封装,实现简单的查询构造器;
- 中间件:在路由分发前后插入鉴权、日志等逻辑;
- 请求与响应对象:将
$_GET、$_POST封装为 Request 类,统一响应格式; - 依赖注入容器:解耦控制器对具体类的依赖;
- 异常处理:统一捕获错误并渲染错误页面。
结语
从零实现一个 MVC 框架,价值不在于替代成熟框架,而在于揭开框架的“黑箱”。路由的本质是 URL 到函数的映射,控制器的本质是请求的协调者,视图的本质是数据与模板的结合。理解了这三者,再去看 Laravel 或 Symfony 的源码,许多设计便会豁然开朗。希望本文能成为你深入 PHP 框架世界的一块垫脚石。
未经允许不得转载:任鹏个人博客 » 基于 PHP 实现一个简易的 MVC 框架:路由、控制器与视图渲染


朋友圈点赞图在线生成源码