去年帮朋友改造一个日访问量两千左右的小型电商站。原先的代码是一个 index.php 文件里塞了商品查询、用户登录、订单处理,甚至还有 HTML 模板。改一个按钮颜色,得在三百多行里找半天。数据库连接直接写在循环里,每次请求都新建连接。那台 1 核 2G 的云服务器,CPU 时不时飙到 90%。
这不是个例。很多人用 PHP 或 Node.js 起步时,习惯把逻辑、数据和界面揉在一起。代码能跑,但加一个功能就像在乱麻里穿针。MVC 不是银弹,但它能把这三股线分开——至少让你知道该去哪儿找那根针。
Model 经常被误解成“就是操作数据库的那层”。我见过不少项目把 SQL 查询直接写在 Model 里,然后 Controller 调一下,View 再渲染。这确实分了三层,但 Model 成了 SQL 拼接器。
真正的 Model 应该封装业务规则。比如一个优惠券系统:满 200 减 30,但特价商品不参与,且每个用户只能用一次。这些判断不该散落在 Controller 的 if-else 里,也不该塞进 View 的模板标签。它们属于 Model。数据库只是 Model 持久化数据的一种方式——你完全可以换成 Redis 或内存缓存,而业务逻辑不变。
// 一个稍微像样的 Model 方法 class Coupon { public function apply(User $user, Cart $cart) { if ($user->hasUsed($this->code)) { throw new Exception('已使用过'); } $eligibleTotal = $cart->getEligibleTotal($this->excludedCategories); if ($eligibleTotal < $this->minAmount) { return 0; } return min($this->discount, $eligibleTotal); } } 这样,Controller 只负责“拿数据、调方法、传结果”,View 只负责“怎么显示”。哪天老板说“满减改成满 300 减 50”,你改一个地方就行。
Controller 最容易膨胀。我见过一个 UserController 里写了注册、登录、改密码、上传头像、发验证邮件、记录登录日志。每个方法两三百行。这哪是 Controller,这是上帝类。
Controller 的职责很窄:接收请求,调用合适的 Model,选择 View。它应该像餐厅服务员——记下客人点的菜,传给后厨,再把菜端上来。而不是自己跑去炒菜。
验证数据?可以放在 Model 或单独的 Validator。发邮件?交给 Service 层或队列。日志?用中间件或事件监听。Controller 里超过 20 行的业务代码,我就觉得不对劲了。
$user = User::find($id); return view('user', compact('user'));。这没问题,但别停在这儿。真实项目里,Controller 一胖,整个应用就难测、难改、难交接。 View 层被讨论得最少,但坑不少。有人把 View 当成“只放 HTML 的地方”,结果所有循环、条件、格式化都塞进模板。一个 foreach 里嵌三层 if,还调用 date() 函数。模板变成了一门蹩脚的编程语言。
我的做法:View 只做展示逻辑。什么是展示逻辑?循环输出列表、根据状态显示不同颜色、格式化日期。什么是业务逻辑?“如果用户是 VIP 且订单超过 500 则打九折”——这属于 Model。View 拿到的是已经算好的 $finalPrice,直接输出就行。
另外,别在 View 里查数据库。我早期犯过这错:在列表页的模板里写 User::find($post->user_id)->name。一个页面 50 条记录,就是 50 次查询。页面加载 3 秒。后来改成 Controller 一次性取出所有关联用户,View 里直接 $post->userName。查询降到 2 次。这种 N+1 问题,MVC 分层清晰时反而更容易暴露——因为你知道该在哪一层解决。
路由配置常被忽视。有人把所有请求都指向一个 index.php,然后在里面用 switch ($_GET['action']) 分发。这相当于把 MVC 的入口做成了大杂烩。
现代框架的路由很灵活。比如 Laravel 里:
Route::get('/products/{ id}', [ProductController::class, 'show']); Route::post('/cart/add', [CartController::class, 'add']); 清晰、可读、易维护。哪天要把 /products/{ id} 改成 /p/{ slug},改一行路由就行,Controller 不用动。路由还承担了中间件绑定、参数验证、频率限制等职责。把它当回事。
最后说点实在的。MVC 是一种组织代码的方式,不是宗教。一个只有三个页面的静态站,硬套 MVC 反而累赘。一个实时聊天应用,可能更适合事件驱动架构。我见过有人用 MVC 写了一个只有 50 行逻辑的爬虫,结果分了 8 个文件。过度设计比不设计更可怕。
判断标准很简单:当你觉得“改一个地方要动五个文件”时,可能分层过度了;当你觉得“改一个地方要在 500 行里找”时,分层不足。MVC 的价值在于让代码可维护、可测试、可协作。如果它没帮你做到这三点,那就调整,别硬撑。
回到开头那个电商站。重构后,Model 处理库存和优惠,Controller 只做参数校验和响应,View 用 Blade 模板。服务器 CPU 降到 15% 左右。朋友后来加了一个“限时抢购”功能,只改了 Model 里的一个方法,加了两个路由。他说:“终于不用在屎山里挖通道了。”
这大概就是 MVC 最朴素的回报。
```