ThinkPHP 8模板路径需小写下划线命名、assign与fetch必须链式调用、跨模块引用加@符号、数组访问须用{volist}或预处理。

模板路径找不到:ThinkPHP 8 默认只认小写下划线控制器名
你写了 UserController,但模板却始终报 Template not found——不是模板写错了,是框架根本没去你放的位置找。ThinkPHP 8 的模板自动匹配逻辑是:把控制器类名做驼峰转小写下划线处理,再拼上 view/ 路径。比如 UserController → user → 查找 app/index/view/user/index.html。
这意味着:
-
UserController对应的模板目录必须是user/(全小写),不是User/或UserController/ -
AdminLoginController会被转成admin_login,所以得放在app/index/view/admin_login/下 - 如果类名含数字或特殊前缀(如
V1UserController),转换结果可能不符合预期,此时建议改类名,或手动指定模板路径:$this->fetch('v1_user/index') - Linux 服务器严格区分大小写,
User和user是两个不同目录;Windows 可能不报错,但上线后必挂
fetch() 链式中断导致变量丢失
$this->assign('data', $list); $this->fetch(); 这样写,模板里 {$data} 一定是空的。ThinkPHP 8 的 assign() 不是全局变量注入,而是暂存到当前响应上下文,必须和 fetch() 在同一调用链上才能生效。
正确写法只有两种:
立即学习“PHP免费学习笔记(深入)”;
-
return $this->assign('data', $list)->fetch();(推荐,语义清晰) -
$this->assign('data', $list); return $this->fetch();(注意是return同一次调用,不是两行独立语句)
常见错误:在中间加了日志、跳转或条件判断,导致链式断开。例如:
if ($debug) {
\think\facade\Log::info('data ready');
}
return $this->assign('data', $list)->fetch(); // ✅ 还在链上
$this->assign('data', $list);
if ($debug) {
\think\facade\Log::info('data ready');
}
return $this->fetch(); // ❌ assign 已失效
跨模块模板引用必须带 @ 符号
想在 app/index/controller/User.php 里引入后台的公共头文件?直接写 $this->fetch('public/header') 是错的——它会去 app/index/view/public/ 找,而不是 app/admin/view/public/。
ThinkPHP 8 要求跨模块必须显式标注模块名,格式为:模块名@子路径:
-
$this->fetch('admin@public/header')→ 查找app/admin/view/public/header.html -
$this->fetch('pc@common/footer')→ 查找app/pc/view/common/footer.html - 漏掉
@就是同模块查找,不会 fallback 到其他模块
注意:@ 前后都不能有空格,也不能写成 admin/@public/header 或 admin@/public/header。
数组变量不能直接点取,必须用 {volist}
模板里写 {$list[0].name} 看似合理,但在 ThinkPHP 8 模板引擎中会静默失败,输出空白。这不是 bug,是设计限制:模板层不支持原生 PHP 数组下标访问语法。
正确方式只有两种:
- 用
{volist}标签遍历:{volist name="list" item="item"}{$item.name}{/volist} - 如果只取第一个,先在控制器里预处理:
$this->assign('first', $list[0] ?? []);,模板里用{$first.name}
这个限制容易被忽略,因为开发时可能恰好 $list 是对象而非数组,或者用了调试工具误判结果——上线后数据结构一变,模板就丢内容。



















