4、控制器(Controller)深入:控制器的责任、请求处理、路由分发、模型与视图的协调以及常见的控制器设计模式。
好,咱们今天来聊聊控制器。说实话,很多初学者觉得控制器就是“写逻辑的地方”,结果把控制器搞得又胖又乱。我见过一个项目,控制器里塞了两千行代码,连数据库查询都写在里面。嗯,那场面,简直是一场灾难。
控制器的核心责任,说白了就三件事:接住请求、调度资源、返回响应。它不负责算账,不负责查数据库,更不负责画页面。它就是个“交通指挥员”。
4.1 控制器的核心责任
我个人习惯把控制器的职责拆成五个点,你记一下:
- 接收输入:从 HTTP 请求中提取参数、表单数据、请求头。
- 参数校验:检查数据格式对不对,比如邮箱是不是真的邮箱。
- 调用模型:把处理好的数据交给模型层去干活。
- 选择视图:根据结果决定返回哪个页面或 JSON。
- 错误处理:出错了别崩,给用户一个友好的提示。
重要原则:控制器里不要出现 SQL 语句,不要出现 HTML 标签。一旦你发现控制器里写了 一个请求进来,到底是怎么走到控制器里的?我画了一张图,你看一眼就明白了。 你看,请求先到路由,路由根据 URL 找到对应的控制器方法,然后控制器去协调模型和视图。整个过程就像流水线,每个环节各司其职。 路由分发,说白了就是“谁负责这件事”。我建议你使用显式路由,而不是隐式路由。为什么? 我曾经在一个遗留项目里看到过隐式路由,控制器和方法名全靠约定,结果找了一个 bug 找了三天——原来是一个方法名拼写错了,路由死活匹配不上。从那以后,我坚持用显式路由。 来看一个简单的路由配置示例: 小技巧:路由命名要遵循 RESTful 风格。你想想看,如果每个接口都叫 控制器怎么协调模型和视图?我总结了一个“三步法”: 看个例子: 注意:不要在控制器里直接操作数据库!我曾经见过有人直接在控制器里写 在实际项目中,我常用的控制器模式有这么几种: 我个人最推荐的是瘦控制器 + 服务层的组合。控制器只负责“接和传”,真正的业务逻辑放到服务层里。这样测试起来也方便,你想想看,测试一个控制器方法,只需要 mock 服务层就行了,多省事。 最后,我分享几个我踩过的坑: 核心总结:控制器就是个“二传手”。它不生产数据,它只是数据的搬运工。把这句话刻在脑子里,你的控制器就不会跑偏。echo "4.2 请求处理流程
4.3 路由分发机制
// 显式路由配置
$router->get('/users', 'UserController@index');
$router->get('/users/{id}', 'UserController@show');
$router->post('/users', 'UserController@store');
$router->put('/users/{id}', 'UserController@update');
$router->delete('/users/{id}', 'UserController@destroy');getUserData、deleteUserRecord,那维护起来得多痛苦。4.4 模型与视图的协调
class UserController {
public function show($id) {
// 1. 从模型拿数据
$user = UserModel::find($id);
// 2. 检查数据是否存在
if (!$user) {
return $this->notFound('用户不存在');
}
// 3. 传给视图
return $this->view('user.profile', ['user' => $user]);
}
}DB::query("SELECT * FROM users"),结果后来数据库改了字段名,满世界找哪里引用了这个字段。那叫一个酸爽。4.5 常见的控制器设计模式
模式名称
适用场景
优点
缺点
瘦控制器
业务逻辑简单的 CRUD
代码清晰,容易测试
模型层会变胖
资源控制器
标准的 RESTful 接口
结构统一,团队协作方便
灵活性稍差
动作控制器
每个动作一个类
单一职责,易于扩展
文件数量多
服务控制器
复杂业务逻辑
业务逻辑抽离到服务层
需要额外维护服务层
4.6 避坑指南