文档目录
模板引擎系统

简介

本文件面向 DouPHP 后台管理系统的模板引擎,系统性说明其工作原理与使用方式。内容涵盖:

  • 模板继承机制(通过 include 组合布局)
  • 变量传递方式(assign + RenderContext)
  • 条件渲染(if/elseif/else)
  • 循环结构(foreach、迭代元信息)
  • 模板文件组织结构(主模板与 inc 局部模板的复用策略)
  • 重大架构升级:统一的子菜单模板系统与中央导航解析机制
  • 新增功能:路由事实变量(route_module/route_action)的自动注入
  • 开发示例(创建新页面模板、使用标签、动态渲染)
  • 性能优化(编译缓存、资源加载)
  • 响应式设计与跨浏览器兼容建议

项目结构

后台模板位于 admin/view 目录,采用"主模板 + 局部模板"的组合模式:

  • 主模板:如 index.htm,负责整体骨架与区域划分
  • 局部模板:位于 admin/view/inc,如 header.tpl、sidebar.tpl、toolbar.tpl、footer.tpl、javascript.tpl、pager.tpl、sub_menu.tpl 等,用于复用公共 UI 片段
  • 模板语言:基于自定义的 DWT 模板语法,支持 {include}、{if}、{foreach}、{url}、{$var} 等标签
graph TB
A["admin/view/index.htm"] --> B["admin/view/inc/header.tpl"]
A --> C["admin/view/inc/sidebar.tpl"]
A --> D["admin/view/inc/toolbar.tpl"]
A --> E["admin/view/inc/footer.tpl"]
A --> F["admin/view/inc/javascript.tpl"]
A --> G["admin/view/inc/sub_menu.tpl"]
G --> H["统一子菜单渲染"]

核心组件

  • 渲染入口与上下文:DouView 提供 assign/fetch/display/renderResource/compileSource 等方法,维护模板变量作用域与编译缓存
  • 编译器管线:DouViewCompiler 组织 Lexer → Parser → CodeGenerator,将模板源转换为可执行的 PHP
  • 词法切分:Lexer 单趟扫描模板源,识别注释、literal、php、普通标签等 Token
  • 标签编译器:针对 if、foreach、include 等标签生成对应 PHP 代码
  • 接口契约:TemplateRendererInterface 定义最小渲染契约(assign + fetch),便于统一视图响应
  • 重大架构升级:统一的子菜单模板系统与中央导航解析器 AdminNavResolver
  • 新增功能:路由事实变量注入机制,提供稳定的路由上下文信息

架构总览

模板渲染从控制器或门面调用开始,经 DouView 解析并编译为 PHP,再在运行时执行输出 HTML。新的架构引入了中央导航解析机制和路由事实变量注入。

sequenceDiagram
participant C as "控制器/门面"
participant V as "DouView"
participant AR as "AdminResolver"
participant ANR as "AdminNavResolver"
participant CC as "DouViewCompiler"
participant L as "Lexer"
participant P as "Parser"
participant CG as "CodeGenerator"
participant R as "RenderContext"
C->>V : assign(变量)
C->>V : fetch("模板名")
V->>AR : resolve()
AR->>ANR : resolve(路由名)
ANR-->>AR : $nav 契约数据
AR->>V : assign('nav', $nav)
V->>CC : compile("模板名", 源)
CC->>L : tokenize(源)
L-->>CC : TokenStream
CC->>P : parse(TokenStream, 模板名)
P-->>CC : AST
CC->>CG : generate(AST, 文本, 模板名)
CG-->>V : 编译后的PHP字符串
V->>R : 设置上下文 vars
V->>V : include 编译产物
V-->>C : 返回HTML

详细组件分析

模板继承机制(组合式布局)

  • 通过 {include file="..."} 将 header、sidebar、toolbar、footer 等片段组合到主模板中,形成页面骨架
  • 支持条件包含与覆盖:例如根据配置决定是否引入 custom 模板,实现主题级覆盖
  • 局部模板内部也可嵌套 include,形成多层复用
  • 重大架构升级:新增统一的 sub_menu.tpl 模板,替代各模块独立的子菜单模板
flowchart TD
Start(["进入主模板"]) --> H["包含头部 header.tpl"]
H --> S["包含侧边栏 sidebar.tpl"]
S --> T["包含工具栏 toolbar.tpl"]
T --> SM["包含统一子菜单 sub_menu.tpl"]
SM --> Body{"是否启用自定义首页?"}
Body --> |是| Custom["包含 index.custom.htm"]
Body --> |否| Default["渲染默认首页区块"]
Custom --> Footer["包含底部 footer.tpl"]
Default --> Footer
Footer --> End(["结束"])

变量传递方式

  • 控制器通过 assign 将数据写入模板上下文,fetch 渲染时由 RenderContext 暴露给模板
  • 模板中使用 {$var} 访问变量;可通过 include 具名传参将结果保存到指定变量
  • 内置安全:支持全局自动 HTML 转义开关,避免 XSS
  • 重大架构升级:新的 $nav 契约提供中央解析的导航数据,包括子菜单、侧边栏状态等
  • 新增功能:路由事实变量自动注入,提供稳定的路由上下文信息

新增路由事实变量机制

前台控制器的 pageFactVars() 方法现在自动注入以下模板变量:

  • route_module:当前请求命中的模块名
  • route_action:当前请求命中的动作名

这些变量为模板中的条件渲染提供了稳定的路由事实基础,特别适用于共用片段的内容分支逻辑。

flowchart TD
FC["前台 BaseController::view()"] --> PFV["pageFactVars()"]
PFV --> RM["注入 route_module"]
PFV --> RA["注入 route_action"]
RM --> TM["模板变量可用"]
RA --> TM
TM --> CB["内容分支判断"]

Section sources

  • DouView.php:84-116
  • IncludeTagCompiler.php:34-68
  • AdminResolver.php:98-100
  • front BaseController.php:81-109

条件渲染(if/elseif/else)

  • 使用 {if}、{elseif}、{else}、{/if} 进行分支控制
  • 条件表达式交由表达式层编译,支持常见比较与逻辑运算
  • 模板中可结合状态变量显示不同内容(如快速菜单开关、更新提示等)
  • 重大架构升级:新的导航系统简化了条件判断逻辑,使用布尔状态而非复杂字符串比较
  • 新增功能:可利用 route_module/route_action 进行基于路由的事实性条件判断
flowchart TD
Enter(["进入条件块"]) --> Check{"条件成立?"}
Check --> |是| Then["执行 then 分支"]
Check --> |否| ElseCheck{"是否有 elseif?"}
ElseCheck --> |有| ElseIf["编译并执行 elseif 条件"]
ElseCheck --> |无| ElseBranch["执行 else 分支"]
Then --> Exit(["结束"])
ElseBranch --> Exit
ElseIf --> Exit

Section sources

  • IfTagCompiler.php:25-54

循环结构(foreach)

  • 使用 {foreach from=数组 item=项 key=键 name=名称 limit=限制 offset=偏移} ... {/foreach}
  • 支持 else 分支处理空列表
  • 循环元数据(total、iteration 等)可通过上下文获取,便于分页与样式控制
  • 重大架构升级:统一的子菜单模板使用 foreach 遍历标准化的子菜单数据结构
flowchart TD
Start(["进入 foreach"]) --> Prepare["准备数组并切片(offset/limit)"]
Prepare --> Loop{"是否存在元素?"}
Loop --> |否| ElseBlock["执行 else 分支"]
Loop --> |是| Iter["遍历元素<br/>记录 iteration/total"]
Iter --> Next{"还有下一个?"}
Next --> |是| Iter
Next --> |否| End(["结束"])
ElseBlock --> End

Section sources

  • ForeachTagCompiler.php:25-114

统一的子菜单模板系统

重大架构升级 模板引擎系统引入了统一的 sub_menu.tpl 模板,替代了之前各模块独立的子菜单模板(如 ai_sub_menu.tpl、chat_sub_menu.tpl、distribution_sub_menu.tpl 等)。这一变更显著简化了模板结构并提升了维护性。

统一子菜单模板设计

新的 sub_menu.tpl 模板采用标准化数据结构:

  • 接收来自 AdminNavResolver 的 $nav.sub_menu 数据
  • 支持图标显示、标题渲染和动态菜单项
  • 自动处理激活状态的 CSS 类添加
flowchart TD
ANR["AdminNavResolver::resolve()"] --> NavData["$nav 契约数据"]
NavData --> SubMenu["sub_menu.tpl 渲染"]
SubMenu --> Template["统一模板输出"]

导航状态管理重构

AdminNavResolver 类提供了中央化的导航状态解析:

  • 基于路由名的精确匹配算法
  • 支持通配符模式和排除规则
  • 计算侧边栏和子菜单的激活状态
  • 提供布尔状态而非复杂字符串比较

向后兼容性

系统保留了旧的子菜单模板作为过渡支持:

  • ai_sub_menu.tpl、chat_sub_menu.tpl 等旧模板仍然存在
  • 支持渐进式迁移到新架构
  • 保持现有功能的稳定性

章节来源

  • sub_menu.tpl:1-12
  • AdminNavResolver.php:24-54
  • AdminNavResolver.php:64-136
  • ai_sub_menu.tpl:1-10
  • chat_sub_menu.tpl:1-16

模板文件组织结构与复用策略

  • 主模板集中管理页面结构与区域划分,减少重复代码
  • 局部模板集中在 inc 目录,按功能拆分(导航、侧边栏、页脚、脚本、分页、子菜单等)
  • 通过 include 组合,实现高内聚、低耦合的模板复用
  • 支持主题覆盖:可在配置中切换 custom 模板,实现外观定制
  • 重大架构升级:统一的子菜单模板减少了模板文件的冗余和维护成本

章节来源

  • index.htm:16-222
  • header.tpl:1-38
  • sub_menu.tpl:1-12

开发示例

  • 创建新的管理页面模板:
    • 新建 admin/view/newpage.htm,引用 inc/header.tpl、inc/sidebar.tpl、inc/toolbar.tpl、inc/footer.tpl、inc/sub_menu.tpl
    • 在控制器中 assign 数据并 view('newpage.htm', $data)
  • 使用模板标签:
    • 变量:{$var}
    • 条件:{if $var} ... {/if}
    • 循环:{foreach from=$list item=item} ... {/foreach}
    • 链接:{url link='admin.xxx'}
    • 包含:{include file="inc/xxx.tpl"}
  • 重大架构升级:新的导航变量使用方式
    • 子菜单数据:$nav.sub_menu.title、$nav.sub_menu.icon、$nav.sub_menu.items
    • 侧边栏状态:$nav.side.[id].is_active、$nav.side.[id].is_all_active、$nav.side.[id].is_category_active
    • 激活节点ID:$nav.side_active_id
  • 新增功能:路由事实变量的使用
    • 模块判断:{if $route_module === 'product'}
    • 动作判断:{if $route_action === 'show'}
    • 内容分支:基于路由信息进行条件渲染
  • 动态内容渲染:
    • 通过控制器传入数组/对象,模板中用 foreach 遍历展示
    • 使用 include 的 assign 参数将子模板结果保存到变量,便于二次处理

章节来源

  • index.htm:16-222
  • IncludeTagCompiler.php:34-68
  • sidebar.tpl:10-90
  • sub_menu.tpl:1-12
  • AdminNavResolver.php:27-35
  • front BaseController.php:101-106

依赖关系分析

  • DouView 依赖 DouViewCompiler 完成编译,Compiler 依赖 Lexer、Parser、CodeGenerator
  • Tag 编译器注册到 TagCompilerRegistry,按需生成 PHP
  • 模板文件通过 include 组合,形成页面结构
  • 重大架构升级:AdminResolver 依赖 AdminNavResolver 生成统一的导航数据,并通过 View::assign 注入模板上下文
  • 新增功能:前台 BaseController 的 pageFactVars() 方法自动注入路由事实变量
classDiagram
class DouView {
+assign()
+fetch()
+display()
+renderResource()
+compileSource()
}
class DouViewCompiler {
+compile()
}
class Lexer {
+tokenize()
}
class Parser
class CodeGenerator
class IncludeTagCompiler
class ForeachTagCompiler
class IfTagCompiler
class AdminResolver {
+resolve()
}
class AdminNavResolver {
+resolve()
+emptyNav()
+routeMatches()
}
class SubMenuTemplate {
+render($nav)
}
class FrontBaseController {
+pageFactVars()
+route_module
+route_action
}
DouView --> DouViewCompiler : "使用"
DouViewCompiler --> Lexer : "词法切分"
DouViewCompiler --> Parser : "语法解析"
DouViewCompiler --> CodeGenerator : "代码生成"
CodeGenerator --> IncludeTagCompiler : "标签编译"
CodeGenerator --> ForeachTagCompiler : "标签编译"
CodeGenerator --> IfTagCompiler : "标签编译"
AdminResolver --> AdminNavResolver : "中央导航解析"
AdminNavResolver --> SubMenuTemplate : "提供数据"
FrontBaseController --> DouView : "注入路由事实变量"

Section sources

  • DouView.php:24-318
  • DouViewCompiler.php:39-206

性能考虑

  • 编译缓存:
    • 使用 CompileCache 依据源文件时间戳与编译修订号判断是否需要重编
    • 开启 compile_check 与 force_compile 控制缓存行为
  • 资源加载优化:
    • 将公共脚本与样式放入 inc/javascript.tpl、css 文件中,减少重复请求
    • 合理使用 {url} 生成静态资源路径,配合 CDN 与版本化
  • 模板复用:
    • 通过 include 复用片段,减少重复渲染成本
    • 重大架构升级:统一的子菜单模板减少了模板文件数量和解析开销
  • 安全与转义:
    • 开启全局自动 HTML 转义,避免 XSS 带来的额外清洗开销
  • 重大架构升级:优化的导航状态计算
    • AdminNavResolver 使用高效的匹配算法,基于路由名的精确匹配
    • 布尔状态评估替代复杂的字符串比较操作
    • 中央解析减少了重复的导航状态计算
  • 新增功能:路由事实变量的性能优势
    • 预计算的路由信息避免了模板中的重复查询
    • 稳定的路由事实减少了条件判断的复杂度

Section sources

  • DouView.php:42-55
  • DouView.php:198-230
  • AdminNavResolver.php:179-224
  • front BaseController.php:101-106

故障排查指南

  • 模板未找到:
    • 检查 resolveTemplatePath 的路径白名单与目录前缀校验,确保模板路径合法且存在于 template_dir
  • 编译错误:
    • 查看 Lexer/Parser/CodeGenerator 生成的中间产物,定位语法问题
  • 变量未生效:
    • 确认控制器已 assign 变量,且在模板中正确访问
  • 包含失败:
    • 检查 include 的 file 属性与路径是否正确,必要时使用绝对路径或相对路径规范
  • 重大架构升级:导航相关问题
    • 检查 AdminNavResolver::resolve() 返回的 $nav 数据结构是否符合预期
    • 确认路由声明中的导航配置是否正确
    • 验证子菜单模板是否正确渲染 $nav.sub_menu 数据
    • 检查侧边栏状态是否正确计算 $nav.side 数据
    • 确认模板中使用的导航变量路径正确(如 $nav.sub_menu.items 而非旧的 $submenu)
  • 新增功能:路由事实变量问题
    • 检查前台 BaseController 的 pageFactVars() 方法是否正常执行
    • 确认引擎级回退机制是否正确设置 route_module 和 route_action 为空串
    • 验证模板中使用的路由变量路径正确($route_module、$route_action)
    • 在非分发回退页面上,确认变量值为空串而非未定义

Section sources

  • DouView.php:232-274
  • IncludeTagCompiler.php:34-68
  • AdminNavResolver.php:64-136
  • sub_menu.tpl:1-12
  • front BaseController.php:81-109
  • front Init.php:506-509

结论

DouPHP 的模板引擎以编译式为核心,提供清晰的渲染管线与灵活的标签体系。通过组合式布局与丰富的标签能力,开发者可以快速构建后台管理界面。借助编译缓存与资源复用策略,系统在性能与安全方面具备良好表现。重大架构升级的统一子菜单模板系统和中央导航解析机制进一步提升了模板的可维护性和渲染性能,而新增的路由事实变量注入机制则为模板内容分支提供了更稳定、更可靠的上下文信息。遵循本文的组织与最佳实践,可进一步提升模板的可维护性与扩展性。

附录

  • 常用标签速查:
    • 变量:{$var}
    • 条件:{if} / {elseif} / {else} / {/if}
    • 循环:{foreach from=... item=...} ... {/foreach}
    • 包含:{include file="..."}
    • 链接:{url link='...'}
  • 重大架构升级:新的导航变量速查:
    • 子菜单标题:$nav.sub_menu.title
    • 子菜单图标:$nav.sub_menu.icon
    • 子菜单项列表:$nav.sub_menu.items
    • 侧边栏状态:$nav.side.[id].is_active
    • 激活节点ID:$nav.side_active_id
  • 新增功能:路由事实变量速查:
    • 模块名:$route_module
    • 动作名:$route_action
    • 用途:模板内容分支、条件渲染、逻辑判断
  • 响应式设计建议:
    • 使用移动端优先的 CSS 框架(如 Bootstrap),结合 meta viewport 与媒体查询
    • 在模板中合理拆分布局,避免在大屏与小屏间出现复杂的重排
  • 跨浏览器兼容性:
    • 避免使用过新的 CSS/JS 特性,或使用 polyfill
    • 测试主流浏览器(Chrome、Firefox、Edge、Safari)下的渲染效果
添加日期:2026-10-05