模块目录名不能和控制器类名同名,否则PSR-4自动加载会因命名空间与路径歧义导致类找不到;模块名须全小写(如admin),控制器用大驼峰(如UserController),冲突时应重命名模块而非类。

模块目录名不能和控制器类名同名
ThinkPHP 6 默认按 app/模块名/ 组织结构,但如果你建了个模块叫 user,又在里头写了个 UserController,运行时大概率报 Class "app\user\controller\UserController" not found——不是类没加载,是自动加载器把 user 当成命名空间一级,和 app\user\controller 冲突了。
根本原因是 ThinkPHP 的 PSR-4 自动加载规则会把模块名直接映射为命名空间段,而 PHP 命名空间不区分大小写,但文件系统路径区分;一旦模块名和类名(尤其是首字母大写的控制器)拼起来形成歧义路径,composer dump-autoload 就可能漏掉这个类。
- 模块名统一用全小写、无下划线的英文名词,比如
admin、api、wechat - 控制器类名必须以大驼峰开头,且避免和模块名重名,
UserController放admin模块没问题,但别放user模块 - 如果已有冲突模块(如
user),不要改类名去迁就,而是重命名模块目录为usermgr或usercenter,再同步更新config/app.php中的default_module和路由配置
跨模块调用必须显式 use 全限定类名
很多人以为在 app/admin/controller/IndexController.php 里直接写 new UserController() 就能调用 app/user/controller/UserController,结果抛出 Class 'app\admin\controller\UserController' not found。这不是 autoload 失效,是你没告诉 PHP 这个类在哪。
ThinkPHP 不做跨模块自动导入,所有跨模块类引用都得靠 use 显式声明,否则解析器默认按当前命名空间补全。
立即学习“PHP免费学习笔记(深入)”;
- 正确写法是开头加
use app\user\controller\UserController;,然后才能new UserController() - 如果只是调用静态方法(比如
UserModel::getById()),同样要use app\user\model\UserModel; - 切勿用
Loader::import()或include手动加载——这绕过自动加载机制,会导致依赖注入失效、事件监听丢失、以及后续升级时被废弃
模块间模型复用需避开 app/model/ 全局目录陷阱
有人把所有模型都扔进 app/model/,以为“全局可用”,结果在 admin 模块调用 User 模型时,发现数据表前缀、连接配置还是走默认库,而不是 admin 模块自己配的读写分离配置。
原因在于:ThinkPHP 的模型类若不在模块专属命名空间下(即不是 app\模块名\model\Xxx),框架就不会自动绑定该模块的数据库配置上下文,db()、connection() 等方法都会回落到全局配置。
- 每个模块需要独立控制数据源的模型,必须放在对应模块目录下,例如
app/user/model/User.php命名空间为app\user\model - 真正通用的模型(如基础字典、日志),可放
app/common/model/,但要用use app\common\model\Dict;显式引入,并手动指定连接:Dict::connect('common_db') - 别在
app/model/下建子目录模拟模块结构——Composer 的 autoload 配置默认不扫描这里,类根本不会被注册
think\facade\App 获取模块名不可靠
写跨模块逻辑时,有人用 \think\facade\App::getModuleName() 判断当前在哪,再拼路径或切换配置,结果在命令行、定时任务、或者多模块共存路由(如 /api/v1/user)场景下返回空或错值。
因为 App::getModuleName() 依赖请求上下文,而 CLI 环境无模块路由解析,它只能返回默认模块;更麻烦的是,当通过 Url::build() 或 Hook::listen() 跨模块触发时,模块上下文可能尚未初始化。
- 需要确定模块身份的地方,优先从调用方传参明确指定,比如
UserService::sync($module = 'admin') - 若必须动态识别,改用
request()->rule()->getModule()(确保已进入路由匹配阶段) - 永远不要在
app/common/或服务提供者中依赖App::getModuleName()做分支逻辑——那里根本没有可靠模块信息
模块边界不是靠目录物理隔离,而是靠命名空间+自动加载+配置作用域三者对齐。一个字母大小写不对、一行 use 缺失、一次 getModuleName() 误用,都可能让跨模块调用在某个环境静默失败。


















