CodeIgniter 应按业务域分组控制器、职责聚合模型、视图路径严格对齐控制器、用服务类解耦而非硬套HMVC;例如 controllers/admin/User.php 对应 views/admin/user/,User_auth_model.php 仅处理登录态,User_service 封装跨模型逻辑。

CodeIgniter 的 MVC 目录结构本身是固定的,但「怎么规划」的关键不在于改目录名,而在于 application/ 内部的组织逻辑和跨模块协作方式——尤其当项目从单页表单发展到多业务线共存时,原始的 controllers/、models/、views/ 平铺结构会迅速失控。
控制器命名要带业务域前缀,别用纯功能名
常见错误是把所有控制器都放在 application/controllers/ 下,起名如 User.php、Order.php、Report.php。看似清晰,但随着权限系统、API 接口、后台管理三端并存,User.php 会同时混入登录、重置密码、导出 Excel、后台禁用用户等逻辑,最终变成“上帝控制器”。
更可行的做法是按访问入口或业务边界分组:
-
application/controllers/api/v1/User.php(REST API) -
application/controllers/admin/User.php(后台管理) -
application/controllers/front/User.php(前台页面)
这样做的好处是:路由可天然隔离($route['api/v1/user'] vs $route['admin/user']),权限控制可按目录统一加载,且 IDE 跳转时不会在一堆同名类里迷路。注意 CI3 默认不支持子目录控制器,需在 config/routes.php 中显式启用:$route['admin/(:any)'] = 'admin/$1';,并在 application/config/autoload.php 中确保 controller 类已自动加载。
模型别只按表名建,要按职责聚合
看到数据库有 users、user_profiles、user_logins 就建三个模型?这是典型陷阱。CI 的 CI_Model 是轻量基类,不强制 ORM 关系,所以模型该承担的是「数据操作契约」,不是「表映射快照」。
比如用户相关操作,建议拆成:
-
User_auth_model.php:只管登录态、token 验证、密码重置 -
User_profile_model.php:只管头像、昵称、收货地址等可编辑字段 -
User_stat_model.php:只管登录次数、最后活跃时间、积分统计
每个模型方法应单一、无副作用(比如 update_profile() 不该顺手发邮件,那是 hooks 或 service 层的事)。如果多个模型要共用 SQL 片段,提取到 application/helpers/db_helper.php,而不是在每个模型里重复写 $this->db->where()。
视图文件夹必须和控制器路径对齐
CI 的 $this->load->view('user/profile') 会去 application/views/user/profile.php 找文件。如果控制器路径是 admin/User.php,但视图还放在 views/user/,那后期维护者根本看不出哪个视图配哪个后台控制器。
正确做法是让视图路径严格跟随控制器路径层级:
-
application/controllers/admin/User.php→application/views/admin/user/index.php -
application/controllers/api/v1/User.php→application/views/api/v1/user/detail.php
这样连调试都省事:报错提示 Unable to load the requested file: api/v1/user/detail.php,一眼定位缺失文件位置。另外,公共片段(如 header、sidebar)统一放 application/views/partials/,避免跨目录硬引用。
CI3 想模块化?别硬套 HMVC,先用服务类解耦
网上很多教程一上来就推 wiredesignz/codeigniter-modular-extensions,但 CI3 原生不支持模块自动加载,装了这个库后,application/modules/ 下的代码在 CLI 环境、队列任务、单元测试中极易失联,而且 Composer 自动加载和 CI 的 load->library() 机制容易冲突。
更稳的过渡方案是:在 application/libraries/ 下写轻量服务类,例如:
class User_service {
public function __construct() {
$this->CI =& get_instance();
$this->CI->load->model('User_auth_model');
$this->CI->load->model('User_profile_model');
}
public function get_full_profile($user_id) {
return [
'auth' => $this->CI->User_auth_model->get_by_id($user_id),
'profile' => $this->CI->User_profile_model->get_by_user_id($user_id)
];
}
}
然后在任意控制器里 $this->load->library('User_service'); 即可复用。它不依赖路由、不触发额外请求周期、测试友好,也方便未来迁移到 CI4 的 Service Container。
真正难的从来不是目录怎么摆,而是每次加新功能时,能不能 3 秒内判断出这段逻辑该进哪个模型、哪个视图、要不要提成服务类——这取决于你第一次建 User_auth_model.php 时,有没有在注释里写清楚它的边界。


















