Template not found 错误八成因路径错误:ThinkPHP 8 默认按 app/模块/view/控制器/操作.html 匹配,路径须全小写、view 与 controller 同级;{include} 和 {extend} 路径以当前模板目录为起点,非 view 根目录;assign() 需链式调用 fetch() 才生效。

Template not found 错误十次里有八次是路径写错,不是语法问题。ThinkPHP 8 的模板引擎默认不报变量未定义警告,而是静默跳过——所以变量取不到、内容空白、include 失败,大概率是目录结构或调用方式不对,不是模板写错了。
模板文件放哪才被自动找到
ThinkPHP 8 默认按 app/{模块名}/view/{控制器名}/{操作名}.html 匹配模板。比如 app\index\controller\User.php 里的 index() 方法,会去找 app\index\view\user\index.html。
- 路径必须全小写(Linux 严格区分大小写,Windows 可能掩盖问题)
-
view目录必须和controller同级,即在app/index/下,不是app/view/ - 用
$this->fetch('profile')时,它找的是当前控制器同级的profile.html,不是view/profile.html - 跨模块引用要带
@:例如$this->fetch('admin@public/header'),漏掉@就会去当前模块下找
assign() 传参后模板里取不到变量
最常见原因是链式断开或作用域丢失。ThinkPHP 8 的 assign() 是“暂存”,不调用 fetch() 或 display() 就不会真正注入上下文。
- ✅ 正确:
$this->assign('title', '首页')->fetch(); - ❌ 错误:
$this->assign('title', '首页'); return $this->fetch();(两次独立调用,第二次 fetch 没拿到 assign 的值) - 数组变量不能直接用
{$list[0].name},必须用{volist name="list" item="vo"}{$vo.name}{/volist} - 系统变量如
{:config('app.name')}需提前在配置中放开函数白名单,否则报错或为空
{include} 和 {extend} 的路径为什么总崩
{include} 和 {extend} 的路径解析起点是「当前模板所在目录」,不是 view 根目录。这是多数人直觉外的点,也是嵌套包含失败的主因。
-
{include file="header"}在user/index.html中,实际找的是view/user/header.html - 想包含公共头,必须写全:
{include file="public/header"}(从view/开始算) -
{extend name="public/base"}要求base.html必须含{block name="body"}{/block},子模板也得用同名{block name="body"}{/block}包裹内容,漏一个就整个 block 不渲染 - 继承时如果父模板没定义
{block},子模板写了也无效,页面会空白
模板缓存关不关、怎么清才有效
开发阶段务必关缓存,否则改了 HTML 看不到效果;但关的位置容易搞错——不是改 config/template.php 里的 'cache' => false 就完事。
立即学习“PHP免费学习笔记(深入)”;
- 缓存文件真实路径是
runtime/html/,不是runtime/cache/,清错目录等于没清 - 命令行清缓存要用:
php think clear --template,只清模板编译缓存 - 如果改了配置(比如定界符或函数白名单)但页面还是旧的,先删
runtime/html/全部文件,再重启服务 - 上线后开缓存可省约 20% 渲染时间,但注意:模板文件修改后,缓存会自动更新,前提是文件权限可写
include 和 extend 的相对路径起点、assign 必须链式到底、缓存位置不在常规 cache 目录——这三点最容易反复踩坑。



















