BaseController 应只放共用生命周期初始化逻辑:服务对象获取、全局助手加载、用户状态检查、请求预处理;禁放业务逻辑与视图封装;必须首行调用 parent::__construct();配置应通过 Config 类读取。

BaseController 里该放哪些初始化逻辑
BaseController 不是“放通用工具函数”的地方,而是集中处理所有控制器共用的生命周期初始化动作。核心原则:只要子控制器一创建就要立刻生效、且后续每个 action 都会依赖的东西,才适合放进来。
- 服务对象的提前获取,比如
$this->session = \Config\Services::session()或$this->db = \Config\Database::connect()—— 这比在每个方法里重复调用更轻量,也避免漏写 - 全局助手函数加载,例如
helper('url')、helper('form')—— 放在构造函数里一次搞定,不用每个方法都 check 是否已加载 - 用户登录状态检查与基础权限设置,比如读取
$this->session->get('user_id')并赋值给$this->currentUser属性,供子类直接使用 - 请求预处理,如统一解析 API 的 token 头、校验签名参数,失败时直接
return $this->failUnauthorized()
别往 BaseController 塞业务逻辑或视图渲染封装
很多人把 _render()、loadViewWithLayout() 这类模板逻辑塞进 BaseController,结果发现:一旦某个控制器要返回 JSON 或纯文本,就得绕开它;或者中间件顺序错乱导致 layout 渲染两次。这不是复用,是耦合。
- 视图封装建议抽成独立服务类(如
ViewRenderer),按需注入,而不是强制继承 - 业务逻辑(如“检查用户是否 VIP”)应该用中间件或策略类实现,BaseController 只负责暴露
$this->currentUser这类原始数据 - 如果硬要加
_render(),务必支持null视图名或"raw"模式,否则遇到 CLI 请求或 API 返回就会报错
构造函数里必须调用 parent::__construct()
CI4 的 BaseController 继承自 \CodeIgniter\Controller,而父类构造函数负责绑定 $this->request、$this->response、过滤器链、服务容器等关键对象。不调用它,$this->request->getPost() 可能返回 null,$this->session->set() 会静默失败。
- 正确写法:
parent::__construct();必须是构造函数第一行(或至少在访问任何框架服务之前) - 错误模式:把
parent::__construct()写在 helper 加载之后,或包裹在 if 条件里 —— 框架内部初始化可能被跳过 - 注意 CI4.5+ 对构造函数参数有严格类型推导,若你加了类型提示(如
UserService $service),容器会自动注入,但前提是parent::__construct()已执行完毕
配置项和常量尽量走 Config 类,别硬编码
像站点标题、默认分页数、API 版本前缀这类值,看似“公共”,但硬写在 BaseController 属性里会导致后期难维护。CI4 的 \Config\* 类才是标准出口。
- 正确方式:定义
app/Config/App.php,在里面声明public $siteName = 'MyApp';,然后在 BaseController 中用$this->config->siteName - 避免:
protected $siteName = 'MyApp';—— 这样无法通过环境变量覆盖,测试时也无法 mock - 敏感配置(如密钥)必须从
.env读取,不要出现在 BaseController 文件中
BaseController 最容易被忽略的复杂点在于:它既是初始化入口,又是继承链起点。一旦里面混入条件逻辑(比如“只在 web 环境加载 session”),就可能破坏 CLI 命令或单元测试的稳定性。保持它尽可能薄,把分支判断往后推到具体方法或中间件里,反而更可控。











