ThinkPHP控制器应继承自自定义基类(如BaseController)而非默认空壳Controller;公共逻辑须放initialize()而非构造函数,避免过早跳转;需调用parent::initialize()确保request初始化;页面与接口控制器应分设WebController和ApiController基类。

ThinkPHP控制器怎么继承公共逻辑
直接继承 Controller 类就行,但别用默认的 app\controller\Controller —— 它只是空壳,没封装任何公共行为。真正该继承的是你自己写的基类,比如 app\controller\BaseController。
常见错误是把登录校验、权限判断、日志记录全塞进构造函数里,结果子控制器一实例化就执行,连 index 方法都还没进,就因未登录被跳转了。这不是继承的问题,是执行时机没想清楚。
- 公共初始化逻辑(如验证登录)应放在
initialize()方法里,它在构造函数之后、具体动作方法之前自动调用 - 避免在构造函数中调用
$this->redirect()或抛异常,否则无法被子类覆盖或干预 - 如果基类需要访问模型,别在构造函数里 new Model,改用
model('User')或依赖注入方式,否则测试时难 mock
BaseController里哪些东西能复用,哪些不能硬塞
能安全复用的是跨多个控制器的通用行为:统一鉴权、请求参数预处理、响应格式封装、多语言切换初始化。不能硬塞的是和业务强耦合的逻辑,比如“订单列表页要查优惠券”,这种写进基类等于污染所有控制器。
一个典型陷阱是把 $this->assign() 写死在 initialize() 里传全局变量(如网站标题、用户信息),看似方便,实则导致子控制器无法屏蔽或替换这些变量,模板渲染时容易覆盖出错。
立即学习“PHP免费学习笔记(深入)”;
- 推荐用
$this->view->share()替代批量assign(),更可控 - 权限检查建议返回布尔值,由子控制器决定是 redirect 还是 throw 异常,而不是基类直接跳转
- 日志记录可封装成
logAction()方法,但不要自动触发,留给人手动调用
为什么有些继承后 $this->request 拿不到参数
不是继承的问题,是子控制器重写了 initialize() 却没调用 parent::initialize()。ThinkPHP 的 Request 对象是在父类 Controller 的 initialize() 中绑定到 $this->request 的,跳过它就等于没初始化请求上下文。
另一个常见原因是用了命令行调用控制器(如 php think run),此时 $this->request 是 CLI 实例,不带 GET/POST 数据,但开发者误以为是继承失效。
- 务必在子类
initialize()第一行写parent::initialize(); - 调试时可用
var_dump($this->request instanceof \think\Request)快速确认对象是否就位 - CLI 场景下要用
$this->request->param()而非get()或post()
接口控制器和页面控制器要不要共用一个 BaseController
不要。页面控制器要渲染模板、处理 session、跳转;接口控制器要输出 JSON、校验 token、屏蔽 XSS 输出。混在一起会导致基类越来越臃肿,最终谁都不敢动。
更合理的是分层:建 WebController 和 ApiController 两个基类,都继承自空的 AbstractController(只放最底层工具方法),再各自封装职责。这样新增一个 H5 接口控制器,就能明确继承 ApiController,不会意外带上模板赋值逻辑。
-
WebController可封装fetch()、redirect()、assignCommon() -
ApiController可封装jsonSuccess()、jsonError()、checkToken() - 两个基类都不应互相继承,避免隐式依赖
继承本身很简单,难的是界定边界——什么该往上提,什么该留在原地。很多人卡在“复用”二字上,结果把基类变成上帝类,改一行,全站报错。真正的复用,是让子类越写越轻,不是越写越怕删。



















