ThinkPHP 6+ 彻底移除 Controller 基类,强制依赖注入与无基类控制器;业务逻辑须下沉至只接收数据的 Service 层,验证、事务、响应等各司其职,控制器仅负责收参、调用、返回。

ThinkPHP 6+ 中 Controller 类已移除,不是“废弃”,是彻底没了
TP6 开始,Controller 不再是框架内置基类,也不再自动注入模型或请求对象。你写的 app\controller\Index 类如果还继承 think\Controller,运行时会直接报错:Class 'think\Controller' not found。这不是警告,是致命错误。
根本原因:TP6 推行「无基类控制器」,所有依赖靠构造函数注入或助手函数获取,强制你面对依赖关系本身。
- 别再手动 require 或 use
think\Controller—— 它在核心包里已被删除 - 如果你从 TP5 升级,
initialize()方法不会自动执行,必须显式调用或改用中间件 - 原来在
Controller基类里封装的success()/error()方法,现在得自己写、抽成 trait,或交给前端统一处理
把业务逻辑从控制器挪到 Service 层,不是加个目录就完事
很多人建了个 app\service 目录,把原来控制器里的 DB 查询复制粘贴过去,起名 UserService,以为就解耦了。但问题没变:方法仍强依赖 Request、Validate、甚至 View,只是换了个地方写面条代码。
真正过渡的关键,是让 Service 方法只接收「数据」,不接收「上下文」。
立即学习“PHP免费学习笔记(深入)”;
-
UserService::createUser(array $data)✅ —— 只要数组,不关心是 POST 还是命令行传入 -
UserService::createUser(Request $request)❌ —— 把 HTTP 细节拖进业务层,测试困难,复用率归零 - 验证逻辑别塞进 Service:应在 Controller 或独立
Validate类里做完,再把干净数据传进去 - 事务控制建议放在 Service 方法内(用
Db::transaction()),但事务边界要清晰,别跨多个 Service 调用硬套一层
控制器只剩三件事:收参数、调 Service、发响应
重构后的控制器应该薄得像张纸。它不处理分页计算、不拼接 SQL、不判断用户权限——那些都该在 Service 或 Policy 层完成。
一个典型的 TP6 控制器方法长这样:
public function store(UserValidate $validate, UserService $service)
{
$data = $validate->check(input('post.')); // 验证 + 收参
$result = $service->create($data); // 纯业务调用
return json(['code' => 0, 'data' => $result]); // 响应格式统一由这里定
}
- 依赖通过构造函数或方法注入,避免用
app()或new硬编码,否则 Service 无法被单元测试 mock -
input()是安全的,但别用input('post.name/a')这种带自动类型转换的写法——Service 层无法预料你悄悄转成了数组 - 不要在控制器里做重定向跳转逻辑(如
$this->redirect()),TP6 已不推荐;改用返回redirect()响应实例,保持响应可被中间件拦截
Service 类命名和粒度最容易失控
刚拆的时候容易两个极端:要么一个 SystemService 包打天下,要么每个接口建一个 UserCreateService、UserUpdateService……结果类爆炸。
更务实的做法是按「领域行为」而非「HTTP 动词」划分:
-
UserRegisterService✅ —— 封装注册全流程(发验证码、写用户、发欢迎邮件) -
UserProfileService✅ —— 管理头像、昵称、隐私设置等个人资料相关操作 -
UserDeleteService❌ —— 删除动作太单薄,应并入UserManageService或直接由UserRepository提供 - Service 类里不要放数据库查询语句(
Db::table()->where()),而是调用Repository;Repository 才负责和 Db/Model 打交道
最常被跳过的一步:Service 方法必须有明确的失败契约。比如 create() 成功返回 array,失败抛 InvalidArgumentException 或自定义异常,而不是返回 false 或空数组——否则上层永远要猜“到底哪里错了”。



















