Model是业务规则守门人而非数据库搬运工,须封装校验、加密、约束等逻辑;View须哑化仅输出HTML;Controller仅调度不执行业务;ThinkPHP需防N+1查询、命名冲突、.env失效等陷阱。

Model层不是数据库搬运工,而是业务规则守门人
很多开发者把Model当成单纯封装SQL的工具类,比如写一个UserModel::getById()只负责查数据,把密码校验、邮箱唯一性判断、订单金额合法性检查全扔给Controller。这违背了MVC中Model的核心职责——它必须承载业务逻辑和数据约束。
✅ 正确做法:注册逻辑应全部封装在Model内,例如UserModel::register($data)内部完成:
- 用filter_var()校验邮箱格式
- 查询数据库确认邮箱未被占用
- 调用password_hash()加密密码
- 抛出InvalidArgumentException等具体异常
❌ 错误示范:
- Controller里直接写if (!preg_match('/^1[3-9]\d{9}$/', $_POST['phone']))
- Model方法返回PDOStatement或未fetch的结果集,导致View里意外触发查询
- Model中出现echo、header()或exit(),破坏可测试性
View必须“哑”,禁止任何主动行为
View文件(如user/profile.php)只做一件事:接收Controller传来的变量,安全输出HTML。它不该知道数据库、不认超全局变量、不new任何对象。
✅ 正确写法:
- <?php echo htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8'); ?>
- 使用<?php echo $title; ?>而非<?= $title ?>(避免短标签兼容问题)
❌ 高危操作:
- 在View里写$model = new UserModel(); $user = $model->getById($_GET['id']);
- require_once 'config/database.php'手动连库,绕过统一配置与权限控制
- 直接输出未过滤的用户输入,引发XSS漏洞
Controller只是调度员,不是业务执行者
Controller的代码行数应该很短——收参→验证→调Model→选View→传数据。它不处理密码加密、文件尺寸判断、时间格式化等细节。
✅ 清晰分工:
- 参数验证用validate()或独立Validator类
- 业务动作交由Model或Service类执行,如$this->orderService->create($data)
- 重定向、状态码设置等响应逻辑保留在Controller,但绝不掺杂数据加工
❌ 典型越界:
- UserController::store()里写20行正则匹配手机号+生成邀请码+计算头像路径
- 在Controller里拼接SQL字符串,埋下SQL注入隐患
- 把分页参数、排序字段等硬编码进Controller,导致复用困难
ThinkPHP专属陷阱:关联、缓存与配置
ThinkPHP的MVC落地常因框架特性产生独特问题,需针对性规避:
立即学习“PHP免费学习笔记(深入)”;
-
N+1查询:遍历用户列表时逐个访问
$user->profile,触发n次额外查询。必须改用User::with('profile')->select()预加载 -
关联命名冲突:若数据表含
order_count字段,关联方法名不能叫orderCount,否则属性覆盖导致取值异常 - .env未生效:文件编码为UTF-8 with BOM、位置不在项目根目录、或入口文件绕过thinkphp/base.php初始化流程,都会使配置失效
-
字段缓存冲突:多模块同名模型(如两个
UserModel)共享同一缓存文件名。解决方式包括关闭字段缓存、区分表前缀或重命名模型 -
save()数据错乱:先
find()再save()却未传参,会更新上次查询结果。务必显式传递数据:$model->save($data)或链式->data($data)->save()
不复杂但容易忽略



















