简介
本文件面向需要构建复杂页面结构的开发者,系统阐述 DouPHP 模板系统的“继承与布局”实践。重点包括:
- 模板复用策略:通过公共片段抽取与组合实现高内聚、低耦合的页面组织
- 布局管理:统一头部、尾部、侧边栏等公共区域的抽取与复用
- 标签使用:{include} 用于片段复用;在现有引擎中,块级覆盖(如 {block})需通过自定义扩展或预处理器实现
- 多级复用:通过多层 include 与变量作用域控制实现复杂页面的模块化拼装
- 最佳实践与常见问题:命名规范、路径安全、缓存与调试建议
说明:当前仓库中的主题模板普遍采用“片段化 + 包含”的方式组织页面,未直接使用 {extends}/{block} 语法。若需块级覆盖能力,可在编译前通过前置过滤器注入相应逻辑,或在 Tag 注册表中新增对应编译器。
项目结构
- 主题模板位于 theme/default,页面以 .dwt 结尾,公共片段位于 inc 目录,以 .tpl 结尾
- 模板引擎核心位于 core/web/template,负责解析、编译与渲染
- 控制器与服务层通过 View 门面调用模板渲染,最终由 DouView 完成资源定位、编译与输出
graph TB
A["控制器/服务"] --> B["视图门面(View)"]
B --> C["DouView(渲染入口)"]
C --> D["解析并定位模板资源"]
D --> E["编译: Lexer→Parser→CodeGenerator"]
E --> F["写入编译产物到缓存"]
F --> G["运行时 include 编译后的 PHP"]
G --> H["输出 HTML"]
核心组件
- DouView:模板渲染主类,负责变量作用域、资源解析、编译缓存、执行编译与输出
- DouViewCompiler:将模板源转换为 PHP 代码,内置多种标签编译器(如 include、if、foreach 等)
- 标签编译器注册表:集中管理各标签的编译行为,便于扩展新标签
- 模板资源:主题下的 .dwt 页面与 .tpl 片段,通过 {include} 组合成完整页面
架构总览
下图展示了从请求到最终输出的关键流程,以及模板片段如何被组合为完整页面。
sequenceDiagram
participant Client as "客户端"
participant Controller as "控制器/服务"
participant View as "视图门面"
participant Engine as "DouView"
participant Compiler as "DouViewCompiler"
participant FS as "文件系统"
participant Runtime as "运行时"
Client->>Controller : 发起页面请求
Controller->>View : 指定模板名
View->>Engine : fetch("模板名")
Engine->>FS : resolveTemplatePath()
FS-->>Engine : 模板绝对路径
Engine->>Compiler : compile(资源名, 源)
Compiler-->>Engine : PHP 字符串
Engine->>FS : 写入编译产物(缓存)
Engine->>Runtime : include(编译产物)
Runtime-->>Client : 返回HTML
详细组件分析
模板引擎核心:DouView
- 职责
- 提供 assign/fetch/display 等对外接口
- 解析模板资源路径,进行安全校验与白名单过滤
- 管理编译缓存,按需重编译
- 运行期绑定上下文变量与作用域
- 关键点
- 允许扩展名:tpl、htm、html、dwt
- 支持全局自动 HTML 转义开关
- 支持前置过滤器,可用于模板源预处理(例如注入继承语义)
模板编译器:DouViewCompiler
- 职责
- 词法分析 → 语法分析 → 代码生成
- 注册并调度内置标签编译器(echo、url、include、assign、if、foreach、list、category、strip、literal、comment、php、delim、break、continue)
- 关键点
- 可配置定界符与全局转义
- 通过 TagCompilerRegistry 统一管理标签编译行为,便于扩展
片段复用:{include} 的使用
- 在主题模板中广泛使用 {include file="inc/xxx.tpl"} 引入公共片段
- 典型片段
- 头部导航:inc/header.tpl
- 页脚、在线服务、轮播、推荐模块等
- 优势
- 减少重复代码,提升可维护性
- 便于统一更新站点级 UI 元素
示例引用
- 首页组合片段:index.dwt:19-42
- 产品页组合片段:product.dwt:23-127
- 头部片段:header.tpl:1-112
继承与块覆盖:现状与建议
- 现状
- 当前主题模板未直接使用 {extends}/{block} 语法,而是通过 {include} 组合片段实现复用
- 引擎已提供可扩展的标签编译体系,可通过注册新标签或前置过滤器实现块级覆盖
- 建议方案
- 方案A:前置过滤器注入
- 在 DouView.registerPrefilter 中解析模板源,识别 {extends} 与 {block},将其转换为 include 链与占位符替换逻辑
- 方案B:新增标签编译器
- 在 DouViewCompiler.getTagCompilerRegistry 中注册新的 ExtendsTagCompiler 与 BlockTagCompiler,实现继承与覆盖语义
- 方案C:约定式布局
- 保持现有 {include} 风格,但约定基础布局模板(如 layout.tpl),子模板仅定义内容区片段并通过 include 组合
- 方案A:前置过滤器注入
多级复用与布局拆分
- 推荐做法
- 将站点级公共部分拆分为 header.tpl、footer.tpl、sidebar.tpl 等片段
- 页面模板通过多次 {include} 组合,形成清晰的层级结构
- 使用变量传递上下文数据,避免跨片段状态污染
- 示例参考
- 首页:组合头部、轮播、推荐、在线服务、页脚
- 产品页:组合头部、商品树、面包屑、商品详情、评论、页脚
依赖关系分析
- 组件耦合
- DouView 依赖 DouViewCompiler 进行编译,依赖 CompileCache 管理缓存
- DouViewCompiler 依赖 Lexer、Parser、CodeGenerator 及 TagCompilerRegistry
- 标签编译器通过注册表解耦,便于扩展
- 外部依赖
- 文件系统:模板源与编译产物的读写
- 运行时:include 执行编译后的 PHP
classDiagram
class DouView {
+assign()
+fetch()
+display()
+renderResource()
+compileSource()
}
class DouViewCompiler {
+compile()
-getLexer()
-getParser()
-getCodeGenerator()
-getTagCompilerRegistry()
}
class CodeGenerator
class TagCompilerRegistry
class IncludeTagCompiler
DouView --> DouViewCompiler : "编译模板"
DouViewCompiler --> CodeGenerator : "生成PHP"
DouViewCompiler --> TagCompilerRegistry : "注册标签"
TagCompilerRegistry --> IncludeTagCompiler : "包含片段"
性能考量
- 编译缓存
- 基于源文件与编译产物时间戳判断是否需要重编译
- 支持强制重编与版本头失效机制
- 资源解析安全
- 白名单扩展名、路径遍历防护、真实路径校验
- 运行时开销
- 合理使用 {include} 避免过深嵌套
- 控制片段体积,避免单次渲染过大
故障排查指南
- 模板无法找到
- 检查模板路径是否包含非法字符或越界访问
- 确认扩展名在白名单内
- 编译失败
- 查看编译缓存目录,确认是否有权限写入
- 检查模板语法是否符合内置标签规范
- 片段未生效
- 确认 {include} 路径正确且文件存在
- 检查变量作用域是否正确传递
- 缓存问题
- 清理 STORAGE_PATH/cache/template 下的编译缓存
- 临时开启 force_compile 验证是否为缓存导致
结论
- 当前项目采用“片段化 + 包含”的模板组织方式,具备良好的可维护性与扩展性
- 若需块级覆盖({extends}/{block}),可通过前置过滤器或新增标签编译器实现
- 建议统一布局拆分、规范命名、合理控制片段粒度,以提升开发效率与运行性能
附录
- 常用片段位置
- 头部:theme/default/inc/header.tpl
- 页脚:theme/default/inc/footer.tpl(按主题实际文件为准)
- 在线服务:theme/default/inc/online_service.tpl
- 页面模板示例
- 首页:theme/default/index.dwt
- 产品页:theme/default/product.dwt